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::vector<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::vector<video::ITexture*>::iterator iter =
459 m_texture_trash.begin(); iter != m_texture_trash.end();
461 video::ITexture *t = *iter;
463 //cleanup trashed texture
464 driver->removeTexture(t);
467 infostream << "~TextureSource() "<< textures_before << "/"
468 << driver->getTextureCount() << std::endl;
471 u32 TextureSource::getTextureId(const std::string &name)
473 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
477 See if texture already exists
479 JMutexAutoLock lock(m_textureinfo_cache_mutex);
480 std::map<std::string, u32>::iterator n;
481 n = m_name_to_id.find(name);
482 if (n != m_name_to_id.end())
491 if (get_current_thread_id() == m_main_thread)
493 return generateTexture(name);
497 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
499 // We're gonna ask the result to be put into here
500 static ResultQueue<std::string, u32, u8, u8> result_queue;
502 // Throw a request in
503 m_get_texture_queue.add(name, 0, 0, &result_queue);
505 /*infostream<<"Waiting for texture from main thread, name=\""
506 <<name<<"\""<<std::endl;*/
511 // Wait result for a second
512 GetResult<std::string, u32, u8, u8>
513 result = result_queue.pop_front(1000);
515 if (result.key == name) {
520 catch(ItemNotFoundException &e)
522 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
527 infostream<<"getTextureId(): Failed"<<std::endl;
532 // Draw an image on top of an another one, using the alpha channel of the
534 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
535 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
537 // Like blit_with_alpha, but only modifies destination pixels that
539 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
540 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
542 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
543 // and modifies any destination pixels that are not fully transparent
544 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
545 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
547 // Apply a mask to an image
548 static void apply_mask(video::IImage *mask, video::IImage *dst,
549 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
551 // Draw or overlay a crack
552 static void draw_crack(video::IImage *crack, video::IImage *dst,
553 bool use_overlay, s32 frame_count, s32 progression,
554 video::IVideoDriver *driver);
557 void brighten(video::IImage *image);
558 // Parse a transform name
559 u32 parseImageTransform(const std::string& s);
560 // Apply transform to image dimension
561 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
562 // Apply transform to image data
563 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
566 This method generates all the textures
568 u32 TextureSource::generateTexture(const std::string &name)
570 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
572 // Empty name means texture 0
574 infostream<<"generateTexture(): name is empty"<<std::endl;
580 See if texture already exists
582 JMutexAutoLock lock(m_textureinfo_cache_mutex);
583 std::map<std::string, u32>::iterator n;
584 n = m_name_to_id.find(name);
585 if (n != m_name_to_id.end()) {
591 Calling only allowed from main thread
593 if (get_current_thread_id() != m_main_thread) {
594 errorstream<<"TextureSource::generateTexture() "
595 "called not from main thread"<<std::endl;
599 video::IVideoDriver *driver = m_device->getVideoDriver();
602 video::IImage *img = generateImage(name);
604 video::ITexture *tex = NULL;
608 img = Align2Npot2(img, driver);
610 // Create texture from resulting image
611 tex = driver->addTexture(name.c_str(), img);
616 Add texture to caches (add NULL textures too)
619 JMutexAutoLock lock(m_textureinfo_cache_mutex);
621 u32 id = m_textureinfo_cache.size();
622 TextureInfo ti(name, tex);
623 m_textureinfo_cache.push_back(ti);
624 m_name_to_id[name] = id;
629 std::string TextureSource::getTextureName(u32 id)
631 JMutexAutoLock lock(m_textureinfo_cache_mutex);
633 if (id >= m_textureinfo_cache.size())
635 errorstream<<"TextureSource::getTextureName(): id="<<id
636 <<" >= m_textureinfo_cache.size()="
637 <<m_textureinfo_cache.size()<<std::endl;
641 return m_textureinfo_cache[id].name;
644 video::ITexture* TextureSource::getTexture(u32 id)
646 JMutexAutoLock lock(m_textureinfo_cache_mutex);
648 if (id >= m_textureinfo_cache.size())
651 return m_textureinfo_cache[id].texture;
654 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
656 u32 actual_id = getTextureId(name);
660 return getTexture(actual_id);
663 void TextureSource::processQueue()
668 //NOTE this is only thread safe for ONE consumer thread!
669 if (!m_get_texture_queue.empty())
671 GetRequest<std::string, u32, u8, u8>
672 request = m_get_texture_queue.pop();
674 /*infostream<<"TextureSource::processQueue(): "
675 <<"got texture request with "
676 <<"name=\""<<request.key<<"\""
679 m_get_texture_queue.pushResult(request, generateTexture(request.key));
683 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
685 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
687 assert(get_current_thread_id() == m_main_thread);
689 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
690 m_source_image_existence.set(name, true);
693 void TextureSource::rebuildImagesAndTextures()
695 JMutexAutoLock lock(m_textureinfo_cache_mutex);
697 video::IVideoDriver* driver = m_device->getVideoDriver();
701 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
702 TextureInfo *ti = &m_textureinfo_cache[i];
703 video::IImage *img = generateImage(ti->name);
705 img = Align2Npot2(img, driver);
706 assert(img->getDimension().Height == npot2(img->getDimension().Height));
707 assert(img->getDimension().Width == npot2(img->getDimension().Width));
709 // Create texture from resulting image
710 video::ITexture *t = NULL;
712 t = driver->addTexture(ti->name.c_str(), img);
715 video::ITexture *t_old = ti->texture;
720 m_texture_trash.push_back(t_old);
724 video::ITexture* TextureSource::generateTextureFromMesh(
725 const TextureFromMeshParams ¶ms)
727 video::IVideoDriver *driver = m_device->getVideoDriver();
731 const GLubyte* renderstr = glGetString(GL_RENDERER);
732 std::string renderer((char*) renderstr);
734 // use no render to texture hack
736 (renderer.find("Adreno") != std::string::npos) ||
737 (renderer.find("Mali") != std::string::npos) ||
738 (renderer.find("Immersion") != std::string::npos) ||
739 (renderer.find("Tegra") != std::string::npos) ||
740 g_settings->getBool("inventory_image_hack")
742 // Get a scene manager
743 scene::ISceneManager *smgr_main = m_device->getSceneManager();
745 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
748 const float scaling = 0.2;
750 scene::IMeshSceneNode* meshnode =
751 smgr->addMeshSceneNode(params.mesh, NULL,
752 -1, v3f(0,0,0), v3f(0,0,0),
753 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
754 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
755 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
756 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
757 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
758 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
760 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
761 params.camera_position, params.camera_lookat);
762 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
763 camera->setProjectionMatrix(params.camera_projection_matrix, false);
765 smgr->setAmbientLight(params.ambient_light);
766 smgr->addLightSceneNode(0,
767 params.light_position,
769 params.light_radius*scaling);
771 core::dimension2d<u32> screen = driver->getScreenSize();
774 driver->beginScene(true, true, video::SColor(0,0,0,0));
775 driver->clearZBuffer();
778 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
780 irr::video::IImage* rawImage =
781 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
783 u8* pixels = static_cast<u8*>(rawImage->lock());
790 core::rect<s32> source(
791 screen.Width /2 - (screen.Width * (scaling / 2)),
792 screen.Height/2 - (screen.Height * (scaling / 2)),
793 screen.Width /2 + (screen.Width * (scaling / 2)),
794 screen.Height/2 + (screen.Height * (scaling / 2))
797 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
798 partsize.Width, partsize.Height, GL_RGBA,
799 GL_UNSIGNED_BYTE, pixels);
803 // Drop scene manager
806 unsigned int pixelcount = partsize.Width*partsize.Height;
809 for (unsigned int i=0; i < pixelcount; i++) {
827 video::IImage* inventory_image =
828 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
830 rawImage->copyToScaling(inventory_image);
833 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
834 inventory_image->drop();
837 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
841 driver->makeColorKeyTexture(rtt, v2s32(0,0));
843 if (params.delete_texture_on_shutdown)
844 m_texture_trash.push_back(rtt);
850 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
852 static bool warned = false;
855 errorstream<<"TextureSource::generateTextureFromMesh(): "
856 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
862 // Create render target texture
863 video::ITexture *rtt = driver->addRenderTargetTexture(
864 params.dim, params.rtt_texture_name.c_str(),
865 video::ECF_A8R8G8B8);
868 errorstream<<"TextureSource::generateTextureFromMesh(): "
869 <<"addRenderTargetTexture returned NULL."<<std::endl;
874 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
875 driver->removeTexture(rtt);
876 errorstream<<"TextureSource::generateTextureFromMesh(): "
877 <<"failed to set render target"<<std::endl;
881 // Get a scene manager
882 scene::ISceneManager *smgr_main = m_device->getSceneManager();
884 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
887 scene::IMeshSceneNode* meshnode =
888 smgr->addMeshSceneNode(params.mesh, NULL,
889 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
890 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
891 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
892 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
893 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
894 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
896 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
897 params.camera_position, params.camera_lookat);
898 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
899 camera->setProjectionMatrix(params.camera_projection_matrix, false);
901 smgr->setAmbientLight(params.ambient_light);
902 smgr->addLightSceneNode(0,
903 params.light_position,
905 params.light_radius);
908 driver->beginScene(true, true, video::SColor(0,0,0,0));
912 // Drop scene manager
915 // Unset render target
916 driver->setRenderTarget(0, false, true, 0);
918 if (params.delete_texture_on_shutdown)
919 m_texture_trash.push_back(rtt);
924 video::IImage* TextureSource::generateImage(const std::string &name)
930 const char separator = '^';
931 const char paren_open = '(';
932 const char paren_close = ')';
934 // Find last separator in the name
935 s32 last_separator_pos = -1;
937 for (s32 i = name.size() - 1; i >= 0; i--) {
940 if (paren_bal == 0) {
941 last_separator_pos = i;
942 i = -1; // break out of loop
946 if (paren_bal == 0) {
947 errorstream << "generateImage(): unbalanced parentheses"
948 << "(extranous '(') while generating texture \""
949 << name << "\"" << std::endl;
962 errorstream << "generateImage(): unbalanced parentheses"
963 << "(missing matching '(') while generating texture \""
964 << name << "\"" << std::endl;
969 video::IImage *baseimg = NULL;
972 If separator was found, make the base image
973 using a recursive call.
975 if (last_separator_pos != -1) {
976 baseimg = generateImage(name.substr(0, last_separator_pos));
980 video::IVideoDriver* driver = m_device->getVideoDriver();
984 Parse out the last part of the name of the image and act
988 std::string last_part_of_name = name.substr(last_separator_pos + 1);
991 If this name is enclosed in parentheses, generate it
992 and blit it onto the base image
994 if (last_part_of_name[0] == paren_open
995 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
996 std::string name2 = last_part_of_name.substr(1,
997 last_part_of_name.size() - 2);
998 video::IImage *tmp = generateImage(name2);
1000 errorstream << "generateImage(): "
1001 "Failed to generate \"" << name2 << "\""
1005 core::dimension2d<u32> dim = tmp->getDimension();
1007 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1008 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1010 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1011 // Generate image according to part of name
1012 errorstream << "generateImage(): "
1013 "Failed to generate \"" << last_part_of_name << "\""
1017 // If no resulting image, print a warning
1018 if (baseimg == NULL) {
1019 errorstream << "generateImage(): baseimg is NULL (attempted to"
1020 " create texture \"" << name << "\")" << std::endl;
1027 #include <GLES/gl.h>
1029 * Check and align image to npot2 if required by hardware
1030 * @param image image to check for npot2 alignment
1031 * @param driver driver to use for image operations
1032 * @return image or copy of image aligned to npot2
1034 video::IImage * Align2Npot2(video::IImage * image,
1035 video::IVideoDriver* driver)
1037 if (image == NULL) {
1041 core::dimension2d<u32> dim = image->getDimension();
1043 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1044 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1048 unsigned int height = npot2(dim.Height);
1049 unsigned int width = npot2(dim.Width);
1051 if ((dim.Height == height) &&
1052 (dim.Width == width)) {
1056 if (dim.Height > height) {
1060 if (dim.Width > width) {
1064 video::IImage *targetimage =
1065 driver->createImage(video::ECF_A8R8G8B8,
1066 core::dimension2d<u32>(width, height));
1068 if (targetimage != NULL) {
1069 image->copyToScaling(targetimage);
1077 bool TextureSource::generateImagePart(std::string part_of_name,
1078 video::IImage *& baseimg)
1080 video::IVideoDriver* driver = m_device->getVideoDriver();
1083 // Stuff starting with [ are special commands
1084 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1086 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1088 image = Align2Npot2(image, driver);
1090 if (image == NULL) {
1091 if (part_of_name != "") {
1092 if (part_of_name.find("_normal.png") == std::string::npos){
1093 errorstream<<"generateImage(): Could not load image \""
1094 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1095 errorstream<<"generateImage(): Creating a dummy"
1096 <<" image for \""<<part_of_name<<"\""<<std::endl;
1098 infostream<<"generateImage(): Could not load normal map \""
1099 <<part_of_name<<"\""<<std::endl;
1100 infostream<<"generateImage(): Creating a dummy"
1101 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1105 // Just create a dummy image
1106 //core::dimension2d<u32> dim(2,2);
1107 core::dimension2d<u32> dim(1,1);
1108 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1110 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1111 image->setPixel(1,0, video::SColor(255,0,255,0));
1112 image->setPixel(0,1, video::SColor(255,0,0,255));
1113 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1114 image->setPixel(0,0, video::SColor(255,myrand()%256,
1115 myrand()%256,myrand()%256));
1116 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1117 myrand()%256,myrand()%256));
1118 image->setPixel(0,1, video::SColor(255,myrand()%256,
1119 myrand()%256,myrand()%256));
1120 image->setPixel(1,1, video::SColor(255,myrand()%256,
1121 myrand()%256,myrand()%256));*/
1124 // If base image is NULL, load as base.
1125 if (baseimg == NULL)
1127 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1129 Copy it this way to get an alpha channel.
1130 Otherwise images with alpha cannot be blitted on
1131 images that don't have alpha in the original file.
1133 core::dimension2d<u32> dim = image->getDimension();
1134 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1135 image->copyTo(baseimg);
1137 // Else blit on base.
1140 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1141 // Size of the copied area
1142 core::dimension2d<u32> dim = image->getDimension();
1143 //core::dimension2d<u32> dim(16,16);
1144 // Position to copy the blitted to in the base image
1145 core::position2d<s32> pos_to(0,0);
1146 // Position to copy the blitted from in the blitted image
1147 core::position2d<s32> pos_from(0,0);
1149 /*image->copyToWithAlpha(baseimg, pos_to,
1150 core::rect<s32>(pos_from, dim),
1151 video::SColor(255,255,255,255),
1153 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1160 // A special texture modification
1162 /*infostream<<"generateImage(): generating special "
1163 <<"modification \""<<part_of_name<<"\""
1169 Adds a cracking texture
1170 N = animation frame count, P = crack progression
1172 if (part_of_name.substr(0,6) == "[crack")
1174 if (baseimg == NULL) {
1175 errorstream<<"generateImagePart(): baseimg == NULL "
1176 <<"for part_of_name=\""<<part_of_name
1177 <<"\", cancelling."<<std::endl;
1181 // Crack image number and overlay option
1182 bool use_overlay = (part_of_name[6] == 'o');
1183 Strfnd sf(part_of_name);
1185 s32 frame_count = stoi(sf.next(":"));
1186 s32 progression = stoi(sf.next(":"));
1191 It is an image with a number of cracking stages
1194 video::IImage *img_crack = m_sourcecache.getOrLoad(
1195 "crack_anylength.png", m_device);
1197 if (img_crack && progression >= 0)
1199 draw_crack(img_crack, baseimg,
1200 use_overlay, frame_count,
1201 progression, driver);
1206 [combine:WxH:X,Y=filename:X,Y=filename2
1207 Creates a bigger texture from an amount of smaller ones
1209 else if (part_of_name.substr(0,8) == "[combine")
1211 Strfnd sf(part_of_name);
1213 u32 w0 = stoi(sf.next("x"));
1214 u32 h0 = stoi(sf.next(":"));
1215 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1216 core::dimension2d<u32> dim(w0,h0);
1217 if (baseimg == NULL) {
1218 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1219 baseimg->fill(video::SColor(0,0,0,0));
1221 while (sf.atend() == false) {
1222 u32 x = stoi(sf.next(","));
1223 u32 y = stoi(sf.next("="));
1224 std::string filename = sf.next(":");
1225 infostream<<"Adding \""<<filename
1226 <<"\" to combined ("<<x<<","<<y<<")"
1228 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1230 core::dimension2d<u32> dim = img->getDimension();
1231 infostream<<"Size "<<dim.Width
1232 <<"x"<<dim.Height<<std::endl;
1233 core::position2d<s32> pos_base(x, y);
1234 video::IImage *img2 =
1235 driver->createImage(video::ECF_A8R8G8B8, dim);
1238 /*img2->copyToWithAlpha(baseimg, pos_base,
1239 core::rect<s32>(v2s32(0,0), dim),
1240 video::SColor(255,255,255,255),
1242 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1245 errorstream << "generateImagePart(): Failed to load image \""
1246 << filename << "\" for [combine" << std::endl;
1253 else if (part_of_name.substr(0,9) == "[brighten")
1255 if (baseimg == NULL) {
1256 errorstream<<"generateImagePart(): baseimg==NULL "
1257 <<"for part_of_name=\""<<part_of_name
1258 <<"\", cancelling."<<std::endl;
1266 Make image completely opaque.
1267 Used for the leaves texture when in old leaves mode, so
1268 that the transparent parts don't look completely black
1269 when simple alpha channel is used for rendering.
1271 else if (part_of_name.substr(0,8) == "[noalpha")
1273 if (baseimg == NULL){
1274 errorstream<<"generateImagePart(): baseimg==NULL "
1275 <<"for part_of_name=\""<<part_of_name
1276 <<"\", cancelling."<<std::endl;
1280 core::dimension2d<u32> dim = baseimg->getDimension();
1282 // Set alpha to full
1283 for (u32 y=0; y<dim.Height; y++)
1284 for (u32 x=0; x<dim.Width; x++)
1286 video::SColor c = baseimg->getPixel(x,y);
1288 baseimg->setPixel(x,y,c);
1293 Convert one color to transparent.
1295 else if (part_of_name.substr(0,11) == "[makealpha:")
1297 if (baseimg == NULL) {
1298 errorstream<<"generateImagePart(): baseimg == NULL "
1299 <<"for part_of_name=\""<<part_of_name
1300 <<"\", cancelling."<<std::endl;
1304 Strfnd sf(part_of_name.substr(11));
1305 u32 r1 = stoi(sf.next(","));
1306 u32 g1 = stoi(sf.next(","));
1307 u32 b1 = stoi(sf.next(""));
1308 std::string filename = sf.next("");
1310 core::dimension2d<u32> dim = baseimg->getDimension();
1312 /*video::IImage *oldbaseimg = baseimg;
1313 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1314 oldbaseimg->copyTo(baseimg);
1315 oldbaseimg->drop();*/
1317 // Set alpha to full
1318 for (u32 y=0; y<dim.Height; y++)
1319 for (u32 x=0; x<dim.Width; x++)
1321 video::SColor c = baseimg->getPixel(x,y);
1323 u32 g = c.getGreen();
1324 u32 b = c.getBlue();
1325 if (!(r == r1 && g == g1 && b == b1))
1328 baseimg->setPixel(x,y,c);
1333 Rotates and/or flips the image.
1335 N can be a number (between 0 and 7) or a transform name.
1336 Rotations are counter-clockwise.
1338 1 R90 rotate by 90 degrees
1339 2 R180 rotate by 180 degrees
1340 3 R270 rotate by 270 degrees
1342 5 FXR90 flip X then rotate by 90 degrees
1344 7 FYR90 flip Y then rotate by 90 degrees
1346 Note: Transform names can be concatenated to produce
1347 their product (applies the first then the second).
1348 The resulting transform will be equivalent to one of the
1349 eight existing ones, though (see: dihedral group).
1351 else if (part_of_name.substr(0,10) == "[transform")
1353 if (baseimg == NULL) {
1354 errorstream<<"generateImagePart(): baseimg == NULL "
1355 <<"for part_of_name=\""<<part_of_name
1356 <<"\", cancelling."<<std::endl;
1360 u32 transform = parseImageTransform(part_of_name.substr(10));
1361 core::dimension2d<u32> dim = imageTransformDimension(
1362 transform, baseimg->getDimension());
1363 video::IImage *image = driver->createImage(
1364 baseimg->getColorFormat(), dim);
1366 imageTransform(transform, baseimg, image);
1371 [inventorycube{topimage{leftimage{rightimage
1372 In every subimage, replace ^ with &.
1373 Create an "inventory cube".
1374 NOTE: This should be used only on its own.
1375 Example (a grass block (not actually used in game):
1376 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1378 else if (part_of_name.substr(0,14) == "[inventorycube")
1380 if (baseimg != NULL){
1381 errorstream<<"generateImagePart(): baseimg != NULL "
1382 <<"for part_of_name=\""<<part_of_name
1383 <<"\", cancelling."<<std::endl;
1387 str_replace(part_of_name, '&', '^');
1388 Strfnd sf(part_of_name);
1390 std::string imagename_top = sf.next("{");
1391 std::string imagename_left = sf.next("{");
1392 std::string imagename_right = sf.next("{");
1394 // Generate images for the faces of the cube
1395 video::IImage *img_top = generateImage(imagename_top);
1396 video::IImage *img_left = generateImage(imagename_left);
1397 video::IImage *img_right = generateImage(imagename_right);
1399 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1400 errorstream << "generateImagePart(): Failed to create textures"
1401 << " for inventorycube \"" << part_of_name << "\""
1403 baseimg = generateImage(imagename_top);
1408 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1409 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1411 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1412 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1414 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1415 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1418 // Create textures from images
1419 video::ITexture *texture_top = driver->addTexture(
1420 (imagename_top + "__temp__").c_str(), img_top);
1421 video::ITexture *texture_left = driver->addTexture(
1422 (imagename_left + "__temp__").c_str(), img_left);
1423 video::ITexture *texture_right = driver->addTexture(
1424 (imagename_right + "__temp__").c_str(), img_right);
1425 assert(texture_top && texture_left && texture_right);
1433 Draw a cube mesh into a render target texture
1435 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1436 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1437 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1438 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1439 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1440 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1441 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1442 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1444 TextureFromMeshParams params;
1446 params.dim.set(64, 64);
1447 params.rtt_texture_name = part_of_name + "_RTT";
1448 // We will delete the rtt texture ourselves
1449 params.delete_texture_on_shutdown = false;
1450 params.camera_position.set(0, 1.0, -1.5);
1451 params.camera_position.rotateXZBy(45);
1452 params.camera_lookat.set(0, 0, 0);
1453 // Set orthogonal projection
1454 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1455 1.65, 1.65, 0, 100);
1457 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1458 params.light_position.set(10, 100, -50);
1459 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1460 params.light_radius = 1000;
1462 video::ITexture *rtt = generateTextureFromMesh(params);
1468 driver->removeTexture(texture_top);
1469 driver->removeTexture(texture_left);
1470 driver->removeTexture(texture_right);
1473 baseimg = generateImage(imagename_top);
1477 // Create image of render target
1478 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1482 driver->removeTexture(rtt);
1484 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1487 image->copyTo(baseimg);
1492 [lowpart:percent:filename
1493 Adds the lower part of a texture
1495 else if (part_of_name.substr(0,9) == "[lowpart:")
1497 Strfnd sf(part_of_name);
1499 u32 percent = stoi(sf.next(":"));
1500 std::string filename = sf.next(":");
1501 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1503 if (baseimg == NULL)
1504 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1505 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1508 core::dimension2d<u32> dim = img->getDimension();
1509 core::position2d<s32> pos_base(0, 0);
1510 video::IImage *img2 =
1511 driver->createImage(video::ECF_A8R8G8B8, dim);
1514 core::position2d<s32> clippos(0, 0);
1515 clippos.Y = dim.Height * (100-percent) / 100;
1516 core::dimension2d<u32> clipdim = dim;
1517 clipdim.Height = clipdim.Height * percent / 100 + 1;
1518 core::rect<s32> cliprect(clippos, clipdim);
1519 img2->copyToWithAlpha(baseimg, pos_base,
1520 core::rect<s32>(v2s32(0,0), dim),
1521 video::SColor(255,255,255,255),
1528 Crops a frame of a vertical animation.
1529 N = frame count, I = frame index
1531 else if (part_of_name.substr(0,15) == "[verticalframe:")
1533 Strfnd sf(part_of_name);
1535 u32 frame_count = stoi(sf.next(":"));
1536 u32 frame_index = stoi(sf.next(":"));
1538 if (baseimg == NULL){
1539 errorstream<<"generateImagePart(): baseimg != NULL "
1540 <<"for part_of_name=\""<<part_of_name
1541 <<"\", cancelling."<<std::endl;
1545 v2u32 frame_size = baseimg->getDimension();
1546 frame_size.Y /= frame_count;
1548 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1551 errorstream<<"generateImagePart(): Could not create image "
1552 <<"for part_of_name=\""<<part_of_name
1553 <<"\", cancelling."<<std::endl;
1557 // Fill target image with transparency
1558 img->fill(video::SColor(0,0,0,0));
1560 core::dimension2d<u32> dim = frame_size;
1561 core::position2d<s32> pos_dst(0, 0);
1562 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1563 baseimg->copyToWithAlpha(img, pos_dst,
1564 core::rect<s32>(pos_src, dim),
1565 video::SColor(255,255,255,255),
1573 Applies a mask to an image
1575 else if (part_of_name.substr(0,6) == "[mask:")
1577 if (baseimg == NULL) {
1578 errorstream << "generateImage(): baseimg == NULL "
1579 << "for part_of_name=\"" << part_of_name
1580 << "\", cancelling." << std::endl;
1583 Strfnd sf(part_of_name);
1585 std::string filename = sf.next(":");
1587 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1589 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1590 img->getDimension());
1592 errorstream << "generateImage(): Failed to load \""
1593 << filename << "\".";
1598 Overlays image with given color
1599 color = color as ColorString
1601 else if (part_of_name.substr(0,10) == "[colorize:") {
1602 Strfnd sf(part_of_name);
1604 std::string color_str = sf.next(":");
1605 std::string ratio_str = sf.next(":");
1607 if (baseimg == NULL) {
1608 errorstream << "generateImagePart(): baseimg != NULL "
1609 << "for part_of_name=\"" << part_of_name
1610 << "\", cancelling." << std::endl;
1614 video::SColor color;
1617 if (!parseColorString(color_str, color, false))
1620 if (is_number(ratio_str))
1621 ratio = mystoi(ratio_str, 0, 255);
1623 core::dimension2d<u32> dim = baseimg->getDimension();
1624 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1627 errorstream << "generateImagePart(): Could not create image "
1628 << "for part_of_name=\"" << part_of_name
1629 << "\", cancelling." << std::endl;
1633 img->fill(video::SColor(color));
1634 // Overlay the colored image
1635 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1640 errorstream << "generateImagePart(): Invalid "
1641 " modification: \"" << part_of_name << "\"" << std::endl;
1649 Draw an image on top of an another one, using the alpha channel of the
1652 This exists because IImage::copyToWithAlpha() doesn't seem to always
1655 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1656 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1658 for (u32 y0=0; y0<size.Y; y0++)
1659 for (u32 x0=0; x0<size.X; x0++)
1661 s32 src_x = src_pos.X + x0;
1662 s32 src_y = src_pos.Y + y0;
1663 s32 dst_x = dst_pos.X + x0;
1664 s32 dst_y = dst_pos.Y + y0;
1665 video::SColor src_c = src->getPixel(src_x, src_y);
1666 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1667 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1668 dst->setPixel(dst_x, dst_y, dst_c);
1673 Draw an image on top of an another one, using the alpha channel of the
1674 source image; only modify fully opaque pixels in destinaion
1676 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1677 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1679 for (u32 y0=0; y0<size.Y; y0++)
1680 for (u32 x0=0; x0<size.X; x0++)
1682 s32 src_x = src_pos.X + x0;
1683 s32 src_y = src_pos.Y + y0;
1684 s32 dst_x = dst_pos.X + x0;
1685 s32 dst_y = dst_pos.Y + y0;
1686 video::SColor src_c = src->getPixel(src_x, src_y);
1687 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1688 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1690 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1691 dst->setPixel(dst_x, dst_y, dst_c);
1697 Draw an image on top of an another one, using the specified ratio
1698 modify all partially-opaque pixels in the destination.
1700 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1701 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1703 for (u32 y0 = 0; y0 < size.Y; y0++)
1704 for (u32 x0 = 0; x0 < size.X; x0++)
1706 s32 src_x = src_pos.X + x0;
1707 s32 src_y = src_pos.Y + y0;
1708 s32 dst_x = dst_pos.X + x0;
1709 s32 dst_y = dst_pos.Y + y0;
1710 video::SColor src_c = src->getPixel(src_x, src_y);
1711 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1712 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1715 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1717 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1718 dst->setPixel(dst_x, dst_y, dst_c);
1724 Apply mask to destination
1726 static void apply_mask(video::IImage *mask, video::IImage *dst,
1727 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1729 for (u32 y0 = 0; y0 < size.Y; y0++) {
1730 for (u32 x0 = 0; x0 < size.X; x0++) {
1731 s32 mask_x = x0 + mask_pos.X;
1732 s32 mask_y = y0 + mask_pos.Y;
1733 s32 dst_x = x0 + dst_pos.X;
1734 s32 dst_y = y0 + dst_pos.Y;
1735 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1736 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1737 dst_c.color &= mask_c.color;
1738 dst->setPixel(dst_x, dst_y, dst_c);
1743 static void draw_crack(video::IImage *crack, video::IImage *dst,
1744 bool use_overlay, s32 frame_count, s32 progression,
1745 video::IVideoDriver *driver)
1747 // Dimension of destination image
1748 core::dimension2d<u32> dim_dst = dst->getDimension();
1749 // Dimension of original image
1750 core::dimension2d<u32> dim_crack = crack->getDimension();
1751 // Count of crack stages
1752 s32 crack_count = dim_crack.Height / dim_crack.Width;
1753 // Limit frame_count
1754 if (frame_count > (s32) dim_dst.Height)
1755 frame_count = dim_dst.Height;
1756 if (frame_count < 1)
1758 // Limit progression
1759 if (progression > crack_count-1)
1760 progression = crack_count-1;
1761 // Dimension of a single crack stage
1762 core::dimension2d<u32> dim_crack_cropped(
1766 // Dimension of the scaled crack stage,
1767 // which is the same as the dimension of a single destination frame
1768 core::dimension2d<u32> dim_crack_scaled(
1770 dim_dst.Height / frame_count
1772 // Create cropped and scaled crack images
1773 video::IImage *crack_cropped = driver->createImage(
1774 video::ECF_A8R8G8B8, dim_crack_cropped);
1775 video::IImage *crack_scaled = driver->createImage(
1776 video::ECF_A8R8G8B8, dim_crack_scaled);
1778 if (crack_cropped && crack_scaled)
1781 v2s32 pos_crack(0, progression*dim_crack.Width);
1782 crack->copyTo(crack_cropped,
1784 core::rect<s32>(pos_crack, dim_crack_cropped));
1785 // Scale crack image by copying
1786 crack_cropped->copyToScaling(crack_scaled);
1787 // Copy or overlay crack image onto each frame
1788 for (s32 i = 0; i < frame_count; ++i)
1790 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1793 blit_with_alpha_overlay(crack_scaled, dst,
1794 v2s32(0,0), dst_pos,
1799 blit_with_alpha(crack_scaled, dst,
1800 v2s32(0,0), dst_pos,
1807 crack_scaled->drop();
1810 crack_cropped->drop();
1813 void brighten(video::IImage *image)
1818 core::dimension2d<u32> dim = image->getDimension();
1820 for (u32 y=0; y<dim.Height; y++)
1821 for (u32 x=0; x<dim.Width; x++)
1823 video::SColor c = image->getPixel(x,y);
1824 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1825 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1826 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1827 image->setPixel(x,y,c);
1831 u32 parseImageTransform(const std::string& s)
1833 int total_transform = 0;
1835 std::string transform_names[8];
1836 transform_names[0] = "i";
1837 transform_names[1] = "r90";
1838 transform_names[2] = "r180";
1839 transform_names[3] = "r270";
1840 transform_names[4] = "fx";
1841 transform_names[6] = "fy";
1843 std::size_t pos = 0;
1844 while(pos < s.size())
1847 for (int i = 0; i <= 7; ++i)
1849 const std::string &name_i = transform_names[i];
1851 if (s[pos] == ('0' + i))
1857 else if (!(name_i.empty()) &&
1858 lowercase(s.substr(pos, name_i.size())) == name_i)
1861 pos += name_i.size();
1868 // Multiply total_transform and transform in the group D4
1871 new_total = (transform + total_transform) % 4;
1873 new_total = (transform - total_transform + 8) % 4;
1874 if ((transform >= 4) ^ (total_transform >= 4))
1877 total_transform = new_total;
1879 return total_transform;
1882 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1884 if (transform % 2 == 0)
1887 return core::dimension2d<u32>(dim.Height, dim.Width);
1890 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1892 if (src == NULL || dst == NULL)
1895 core::dimension2d<u32> srcdim = src->getDimension();
1896 core::dimension2d<u32> dstdim = dst->getDimension();
1898 assert(dstdim == imageTransformDimension(transform, srcdim));
1899 assert(transform <= 7);
1902 Compute the transformation from source coordinates (sx,sy)
1903 to destination coordinates (dx,dy).
1907 if (transform == 0) // identity
1908 sxn = 0, syn = 2; // sx = dx, sy = dy
1909 else if (transform == 1) // rotate by 90 degrees ccw
1910 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1911 else if (transform == 2) // rotate by 180 degrees
1912 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1913 else if (transform == 3) // rotate by 270 degrees ccw
1914 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1915 else if (transform == 4) // flip x
1916 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1917 else if (transform == 5) // flip x then rotate by 90 degrees ccw
1918 sxn = 2, syn = 0; // sx = dy, sy = dx
1919 else if (transform == 6) // flip y
1920 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1921 else if (transform == 7) // flip y then rotate by 90 degrees ccw
1922 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1924 for (u32 dy=0; dy<dstdim.Height; dy++)
1925 for (u32 dx=0; dx<dstdim.Width; dx++)
1927 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1928 u32 sx = entries[sxn];
1929 u32 sy = entries[syn];
1930 video::SColor c = src->getPixel(sx,sy);
1931 dst->setPixel(dx,dy,c);
1935 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1938 if (isKnownSourceImage("override_normal.png"))
1939 return getTexture("override_normal.png", &id);
1940 std::string fname_base = name;
1941 std::string normal_ext = "_normal.png";
1942 size_t pos = fname_base.find(".");
1943 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1944 if (isKnownSourceImage(fname_normal)) {
1945 // look for image extension and replace it
1947 while ((i = fname_base.find(".", i)) != std::string::npos) {
1948 fname_base.replace(i, 4, normal_ext);
1949 i += normal_ext.length();
1951 return getTexture(fname_base, &id);