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.c_str());
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.c_str());
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 // Apply a mask to an image
544 static void apply_mask(video::IImage *mask, video::IImage *dst,
545 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
547 // Draw or overlay a crack
548 static void draw_crack(video::IImage *crack, video::IImage *dst,
549 bool use_overlay, s32 frame_count, s32 progression,
550 video::IVideoDriver *driver);
553 void brighten(video::IImage *image);
554 // Parse a transform name
555 u32 parseImageTransform(const std::string& s);
556 // Apply transform to image dimension
557 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
558 // Apply transform to image data
559 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
562 This method generates all the textures
564 u32 TextureSource::generateTexture(const std::string &name)
566 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
568 // Empty name means texture 0
570 infostream<<"generateTexture(): name is empty"<<std::endl;
576 See if texture already exists
578 JMutexAutoLock lock(m_textureinfo_cache_mutex);
579 std::map<std::string, u32>::iterator n;
580 n = m_name_to_id.find(name);
581 if (n != m_name_to_id.end()) {
587 Calling only allowed from main thread
589 if (get_current_thread_id() != m_main_thread) {
590 errorstream<<"TextureSource::generateTexture() "
591 "called not from main thread"<<std::endl;
595 video::IVideoDriver *driver = m_device->getVideoDriver();
598 video::IImage *img = generateImage(name);
600 video::ITexture *tex = NULL;
604 img = Align2Npot2(img, driver);
606 // Create texture from resulting image
607 tex = driver->addTexture(name.c_str(), img);
612 Add texture to caches (add NULL textures too)
615 JMutexAutoLock lock(m_textureinfo_cache_mutex);
617 u32 id = m_textureinfo_cache.size();
618 TextureInfo ti(name, tex);
619 m_textureinfo_cache.push_back(ti);
620 m_name_to_id[name] = id;
625 std::string TextureSource::getTextureName(u32 id)
627 JMutexAutoLock lock(m_textureinfo_cache_mutex);
629 if(id >= m_textureinfo_cache.size())
631 errorstream<<"TextureSource::getTextureName(): id="<<id
632 <<" >= m_textureinfo_cache.size()="
633 <<m_textureinfo_cache.size()<<std::endl;
637 return m_textureinfo_cache[id].name;
640 video::ITexture* TextureSource::getTexture(u32 id)
642 JMutexAutoLock lock(m_textureinfo_cache_mutex);
644 if(id >= m_textureinfo_cache.size())
647 return m_textureinfo_cache[id].texture;
650 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
652 u32 actual_id = getTextureId(name);
656 return getTexture(actual_id);
659 void TextureSource::processQueue()
664 //NOTE this is only thread safe for ONE consumer thread!
665 if(!m_get_texture_queue.empty())
667 GetRequest<std::string, u32, u8, u8>
668 request = m_get_texture_queue.pop();
670 /*infostream<<"TextureSource::processQueue(): "
671 <<"got texture request with "
672 <<"name=\""<<request.key<<"\""
675 m_get_texture_queue.pushResult(request, generateTexture(request.key));
679 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
681 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
683 assert(get_current_thread_id() == m_main_thread);
685 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
686 m_source_image_existence.set(name, true);
689 void TextureSource::rebuildImagesAndTextures()
691 JMutexAutoLock lock(m_textureinfo_cache_mutex);
693 video::IVideoDriver* driver = m_device->getVideoDriver();
697 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
698 TextureInfo *ti = &m_textureinfo_cache[i];
699 video::IImage *img = generateImage(ti->name);
701 img = Align2Npot2(img, driver);
702 assert(img->getDimension().Height == npot2(img->getDimension().Height));
703 assert(img->getDimension().Width == npot2(img->getDimension().Width));
705 // Create texture from resulting image
706 video::ITexture *t = NULL;
708 t = driver->addTexture(ti->name.c_str(), img);
711 video::ITexture *t_old = ti->texture;
716 m_texture_trash.push_back(t_old);
720 video::ITexture* TextureSource::generateTextureFromMesh(
721 const TextureFromMeshParams ¶ms)
723 video::IVideoDriver *driver = m_device->getVideoDriver();
727 const GLubyte* renderstr = glGetString(GL_RENDERER);
728 std::string renderer((char*) renderstr);
730 // use no render to texture hack
732 (renderer.find("Adreno") != std::string::npos) ||
733 (renderer.find("Mali") != std::string::npos) ||
734 (renderer.find("Immersion") != std::string::npos) ||
735 (renderer.find("Tegra") != std::string::npos) ||
736 g_settings->getBool("inventory_image_hack")
738 // Get a scene manager
739 scene::ISceneManager *smgr_main = m_device->getSceneManager();
741 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
744 const float scaling = 0.2;
746 scene::IMeshSceneNode* meshnode =
747 smgr->addMeshSceneNode(params.mesh, NULL,
748 -1, v3f(0,0,0), v3f(0,0,0),
749 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
750 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
751 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
752 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
753 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
754 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
756 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
757 params.camera_position, params.camera_lookat);
758 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
759 camera->setProjectionMatrix(params.camera_projection_matrix, false);
761 smgr->setAmbientLight(params.ambient_light);
762 smgr->addLightSceneNode(0,
763 params.light_position,
765 params.light_radius*scaling);
767 core::dimension2d<u32> screen = driver->getScreenSize();
770 driver->beginScene(true, true, video::SColor(0,0,0,0));
771 driver->clearZBuffer();
774 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
776 irr::video::IImage* rawImage =
777 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
779 u8* pixels = static_cast<u8*>(rawImage->lock());
786 core::rect<s32> source(
787 screen.Width /2 - (screen.Width * (scaling / 2)),
788 screen.Height/2 - (screen.Height * (scaling / 2)),
789 screen.Width /2 + (screen.Width * (scaling / 2)),
790 screen.Height/2 + (screen.Height * (scaling / 2))
793 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
794 partsize.Width, partsize.Height, GL_RGBA,
795 GL_UNSIGNED_BYTE, pixels);
799 // Drop scene manager
802 unsigned int pixelcount = partsize.Width*partsize.Height;
805 for (unsigned int i=0; i < pixelcount; i++) {
823 video::IImage* inventory_image =
824 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
826 rawImage->copyToScaling(inventory_image);
829 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
830 inventory_image->drop();
833 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
837 driver->makeColorKeyTexture(rtt, v2s32(0,0));
839 if(params.delete_texture_on_shutdown)
840 m_texture_trash.push_back(rtt);
846 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
848 static bool warned = false;
851 errorstream<<"TextureSource::generateTextureFromMesh(): "
852 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
858 // Create render target texture
859 video::ITexture *rtt = driver->addRenderTargetTexture(
860 params.dim, params.rtt_texture_name.c_str(),
861 video::ECF_A8R8G8B8);
864 errorstream<<"TextureSource::generateTextureFromMesh(): "
865 <<"addRenderTargetTexture returned NULL."<<std::endl;
870 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
871 driver->removeTexture(rtt);
872 errorstream<<"TextureSource::generateTextureFromMesh(): "
873 <<"failed to set render target"<<std::endl;
877 // Get a scene manager
878 scene::ISceneManager *smgr_main = m_device->getSceneManager();
880 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
883 scene::IMeshSceneNode* meshnode =
884 smgr->addMeshSceneNode(params.mesh, NULL,
885 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
886 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
887 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
888 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
889 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
890 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
892 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
893 params.camera_position, params.camera_lookat);
894 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
895 camera->setProjectionMatrix(params.camera_projection_matrix, false);
897 smgr->setAmbientLight(params.ambient_light);
898 smgr->addLightSceneNode(0,
899 params.light_position,
901 params.light_radius);
904 driver->beginScene(true, true, video::SColor(0,0,0,0));
908 // Drop scene manager
911 // Unset render target
912 driver->setRenderTarget(0, false, true, 0);
914 if(params.delete_texture_on_shutdown)
915 m_texture_trash.push_back(rtt);
920 video::IImage* TextureSource::generateImage(const std::string &name)
926 const char separator = '^';
927 const char paren_open = '(';
928 const char paren_close = ')';
930 // Find last separator in the name
931 s32 last_separator_pos = -1;
933 for(s32 i = name.size() - 1; i >= 0; i--) {
936 if (paren_bal == 0) {
937 last_separator_pos = i;
938 i = -1; // break out of loop
942 if (paren_bal == 0) {
943 errorstream << "generateImage(): unbalanced parentheses"
944 << "(extranous '(') while generating texture \""
945 << name << "\"" << std::endl;
958 errorstream << "generateImage(): unbalanced parentheses"
959 << "(missing matching '(') while generating texture \""
960 << name << "\"" << std::endl;
965 video::IImage *baseimg = NULL;
968 If separator was found, make the base image
969 using a recursive call.
971 if (last_separator_pos != -1) {
972 baseimg = generateImage(name.substr(0, last_separator_pos));
976 video::IVideoDriver* driver = m_device->getVideoDriver();
980 Parse out the last part of the name of the image and act
984 std::string last_part_of_name = name.substr(last_separator_pos + 1);
987 If this name is enclosed in parentheses, generate it
988 and blit it onto the base image
990 if (last_part_of_name[0] == paren_open
991 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
992 std::string name2 = last_part_of_name.substr(1,
993 last_part_of_name.size() - 2);
994 video::IImage *tmp = generateImage(name2);
996 errorstream << "generateImage(): "
997 "Failed to generate \"" << name2 << "\""
1001 core::dimension2d<u32> dim = tmp->getDimension();
1003 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1004 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1006 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1007 // Generate image according to part of name
1008 errorstream << "generateImage(): "
1009 "Failed to generate \"" << last_part_of_name << "\""
1013 // If no resulting image, print a warning
1014 if (baseimg == NULL) {
1015 errorstream << "generateImage(): baseimg is NULL (attempted to"
1016 " create texture \"" << name << "\")" << std::endl;
1023 #include <GLES/gl.h>
1025 * Check and align image to npot2 if required by hardware
1026 * @param image image to check for npot2 alignment
1027 * @param driver driver to use for image operations
1028 * @return image or copy of image aligned to npot2
1030 video::IImage * Align2Npot2(video::IImage * image,
1031 video::IVideoDriver* driver)
1037 core::dimension2d<u32> dim = image->getDimension();
1039 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1040 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1044 unsigned int height = npot2(dim.Height);
1045 unsigned int width = npot2(dim.Width);
1047 if ((dim.Height == height) &&
1048 (dim.Width == width)) {
1052 if (dim.Height > height) {
1056 if (dim.Width > width) {
1060 video::IImage *targetimage =
1061 driver->createImage(video::ECF_A8R8G8B8,
1062 core::dimension2d<u32>(width, height));
1064 if (targetimage != NULL) {
1065 image->copyToScaling(targetimage);
1073 bool TextureSource::generateImagePart(std::string part_of_name,
1074 video::IImage *& baseimg)
1076 video::IVideoDriver* driver = m_device->getVideoDriver();
1079 // Stuff starting with [ are special commands
1080 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1082 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1084 image = Align2Npot2(image, driver);
1086 if (image == NULL) {
1087 if (part_of_name != "") {
1088 if (part_of_name.find("_normal.png") == std::string::npos){
1089 errorstream<<"generateImage(): Could not load image \""
1090 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1091 errorstream<<"generateImage(): Creating a dummy"
1092 <<" image for \""<<part_of_name<<"\""<<std::endl;
1094 infostream<<"generateImage(): Could not load normal map \""
1095 <<part_of_name<<"\""<<std::endl;
1096 infostream<<"generateImage(): Creating a dummy"
1097 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1101 // Just create a dummy image
1102 //core::dimension2d<u32> dim(2,2);
1103 core::dimension2d<u32> dim(1,1);
1104 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1106 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1107 image->setPixel(1,0, video::SColor(255,0,255,0));
1108 image->setPixel(0,1, video::SColor(255,0,0,255));
1109 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1110 image->setPixel(0,0, video::SColor(255,myrand()%256,
1111 myrand()%256,myrand()%256));
1112 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1113 myrand()%256,myrand()%256));
1114 image->setPixel(0,1, video::SColor(255,myrand()%256,
1115 myrand()%256,myrand()%256));
1116 image->setPixel(1,1, video::SColor(255,myrand()%256,
1117 myrand()%256,myrand()%256));*/
1120 // If base image is NULL, load as base.
1123 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1125 Copy it this way to get an alpha channel.
1126 Otherwise images with alpha cannot be blitted on
1127 images that don't have alpha in the original file.
1129 core::dimension2d<u32> dim = image->getDimension();
1130 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1131 image->copyTo(baseimg);
1133 // Else blit on base.
1136 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1137 // Size of the copied area
1138 core::dimension2d<u32> dim = image->getDimension();
1139 //core::dimension2d<u32> dim(16,16);
1140 // Position to copy the blitted to in the base image
1141 core::position2d<s32> pos_to(0,0);
1142 // Position to copy the blitted from in the blitted image
1143 core::position2d<s32> pos_from(0,0);
1145 /*image->copyToWithAlpha(baseimg, pos_to,
1146 core::rect<s32>(pos_from, dim),
1147 video::SColor(255,255,255,255),
1149 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1156 // A special texture modification
1158 /*infostream<<"generateImage(): generating special "
1159 <<"modification \""<<part_of_name<<"\""
1165 Adds a cracking texture
1166 N = animation frame count, P = crack progression
1168 if(part_of_name.substr(0,6) == "[crack")
1170 if (baseimg == NULL) {
1171 errorstream<<"generateImagePart(): baseimg == NULL "
1172 <<"for part_of_name=\""<<part_of_name
1173 <<"\", cancelling."<<std::endl;
1177 // Crack image number and overlay option
1178 bool use_overlay = (part_of_name[6] == 'o');
1179 Strfnd sf(part_of_name);
1181 s32 frame_count = stoi(sf.next(":"));
1182 s32 progression = stoi(sf.next(":"));
1187 It is an image with a number of cracking stages
1190 video::IImage *img_crack = m_sourcecache.getOrLoad(
1191 "crack_anylength.png", m_device);
1193 if(img_crack && progression >= 0)
1195 draw_crack(img_crack, baseimg,
1196 use_overlay, frame_count,
1197 progression, driver);
1202 [combine:WxH:X,Y=filename:X,Y=filename2
1203 Creates a bigger texture from an amount of smaller ones
1205 else if(part_of_name.substr(0,8) == "[combine")
1207 Strfnd sf(part_of_name);
1209 u32 w0 = stoi(sf.next("x"));
1210 u32 h0 = stoi(sf.next(":"));
1211 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1212 core::dimension2d<u32> dim(w0,h0);
1213 if (baseimg == NULL) {
1214 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1215 baseimg->fill(video::SColor(0,0,0,0));
1217 while (sf.atend() == false) {
1218 u32 x = stoi(sf.next(","));
1219 u32 y = stoi(sf.next("="));
1220 std::string filename = sf.next(":");
1221 infostream<<"Adding \""<<filename
1222 <<"\" to combined ("<<x<<","<<y<<")"
1224 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1226 core::dimension2d<u32> dim = img->getDimension();
1227 infostream<<"Size "<<dim.Width
1228 <<"x"<<dim.Height<<std::endl;
1229 core::position2d<s32> pos_base(x, y);
1230 video::IImage *img2 =
1231 driver->createImage(video::ECF_A8R8G8B8, dim);
1234 /*img2->copyToWithAlpha(baseimg, pos_base,
1235 core::rect<s32>(v2s32(0,0), dim),
1236 video::SColor(255,255,255,255),
1238 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1241 errorstream << "generateImagePart(): Failed to load image \""
1242 << filename << "\" for [combine" << std::endl;
1249 else if(part_of_name.substr(0,9) == "[brighten")
1251 if (baseimg == NULL) {
1252 errorstream<<"generateImagePart(): baseimg==NULL "
1253 <<"for part_of_name=\""<<part_of_name
1254 <<"\", cancelling."<<std::endl;
1262 Make image completely opaque.
1263 Used for the leaves texture when in old leaves mode, so
1264 that the transparent parts don't look completely black
1265 when simple alpha channel is used for rendering.
1267 else if(part_of_name.substr(0,8) == "[noalpha")
1269 if (baseimg == NULL){
1270 errorstream<<"generateImagePart(): baseimg==NULL "
1271 <<"for part_of_name=\""<<part_of_name
1272 <<"\", cancelling."<<std::endl;
1276 core::dimension2d<u32> dim = baseimg->getDimension();
1278 // Set alpha to full
1279 for(u32 y=0; y<dim.Height; y++)
1280 for(u32 x=0; x<dim.Width; x++)
1282 video::SColor c = baseimg->getPixel(x,y);
1284 baseimg->setPixel(x,y,c);
1289 Convert one color to transparent.
1291 else if(part_of_name.substr(0,11) == "[makealpha:")
1293 if (baseimg == NULL) {
1294 errorstream<<"generateImagePart(): baseimg == NULL "
1295 <<"for part_of_name=\""<<part_of_name
1296 <<"\", cancelling."<<std::endl;
1300 Strfnd sf(part_of_name.substr(11));
1301 u32 r1 = stoi(sf.next(","));
1302 u32 g1 = stoi(sf.next(","));
1303 u32 b1 = stoi(sf.next(""));
1304 std::string filename = sf.next("");
1306 core::dimension2d<u32> dim = baseimg->getDimension();
1308 /*video::IImage *oldbaseimg = baseimg;
1309 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1310 oldbaseimg->copyTo(baseimg);
1311 oldbaseimg->drop();*/
1313 // Set alpha to full
1314 for(u32 y=0; y<dim.Height; y++)
1315 for(u32 x=0; x<dim.Width; x++)
1317 video::SColor c = baseimg->getPixel(x,y);
1319 u32 g = c.getGreen();
1320 u32 b = c.getBlue();
1321 if(!(r == r1 && g == g1 && b == b1))
1324 baseimg->setPixel(x,y,c);
1329 Rotates and/or flips the image.
1331 N can be a number (between 0 and 7) or a transform name.
1332 Rotations are counter-clockwise.
1334 1 R90 rotate by 90 degrees
1335 2 R180 rotate by 180 degrees
1336 3 R270 rotate by 270 degrees
1338 5 FXR90 flip X then rotate by 90 degrees
1340 7 FYR90 flip Y then rotate by 90 degrees
1342 Note: Transform names can be concatenated to produce
1343 their product (applies the first then the second).
1344 The resulting transform will be equivalent to one of the
1345 eight existing ones, though (see: dihedral group).
1347 else if(part_of_name.substr(0,10) == "[transform")
1349 if (baseimg == NULL) {
1350 errorstream<<"generateImagePart(): baseimg == NULL "
1351 <<"for part_of_name=\""<<part_of_name
1352 <<"\", cancelling."<<std::endl;
1356 u32 transform = parseImageTransform(part_of_name.substr(10));
1357 core::dimension2d<u32> dim = imageTransformDimension(
1358 transform, baseimg->getDimension());
1359 video::IImage *image = driver->createImage(
1360 baseimg->getColorFormat(), dim);
1362 imageTransform(transform, baseimg, image);
1367 [inventorycube{topimage{leftimage{rightimage
1368 In every subimage, replace ^ with &.
1369 Create an "inventory cube".
1370 NOTE: This should be used only on its own.
1371 Example (a grass block (not actually used in game):
1372 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1374 else if(part_of_name.substr(0,14) == "[inventorycube")
1376 if (baseimg != NULL){
1377 errorstream<<"generateImagePart(): baseimg != NULL "
1378 <<"for part_of_name=\""<<part_of_name
1379 <<"\", cancelling."<<std::endl;
1383 str_replace(part_of_name, '&', '^');
1384 Strfnd sf(part_of_name);
1386 std::string imagename_top = sf.next("{");
1387 std::string imagename_left = sf.next("{");
1388 std::string imagename_right = sf.next("{");
1390 // Generate images for the faces of the cube
1391 video::IImage *img_top = generateImage(imagename_top);
1392 video::IImage *img_left = generateImage(imagename_left);
1393 video::IImage *img_right = generateImage(imagename_right);
1395 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1396 errorstream << "generateImagePart(): Failed to create textures"
1397 << " for inventorycube \"" << part_of_name << "\""
1399 baseimg = generateImage(imagename_top);
1404 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1405 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1407 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1408 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1410 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1411 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1414 // Create textures from images
1415 video::ITexture *texture_top = driver->addTexture(
1416 (imagename_top + "__temp__").c_str(), img_top);
1417 video::ITexture *texture_left = driver->addTexture(
1418 (imagename_left + "__temp__").c_str(), img_left);
1419 video::ITexture *texture_right = driver->addTexture(
1420 (imagename_right + "__temp__").c_str(), img_right);
1421 assert(texture_top && texture_left && texture_right);
1429 Draw a cube mesh into a render target texture
1431 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1432 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1433 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1434 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1435 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1436 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1437 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1438 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1440 TextureFromMeshParams params;
1442 params.dim.set(64, 64);
1443 params.rtt_texture_name = part_of_name + "_RTT";
1444 // We will delete the rtt texture ourselves
1445 params.delete_texture_on_shutdown = false;
1446 params.camera_position.set(0, 1.0, -1.5);
1447 params.camera_position.rotateXZBy(45);
1448 params.camera_lookat.set(0, 0, 0);
1449 // Set orthogonal projection
1450 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1451 1.65, 1.65, 0, 100);
1453 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1454 params.light_position.set(10, 100, -50);
1455 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1456 params.light_radius = 1000;
1458 video::ITexture *rtt = generateTextureFromMesh(params);
1464 driver->removeTexture(texture_top);
1465 driver->removeTexture(texture_left);
1466 driver->removeTexture(texture_right);
1469 baseimg = generateImage(imagename_top);
1473 // Create image of render target
1474 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1478 driver->removeTexture(rtt);
1480 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1483 image->copyTo(baseimg);
1488 [lowpart:percent:filename
1489 Adds the lower part of a texture
1491 else if(part_of_name.substr(0,9) == "[lowpart:")
1493 Strfnd sf(part_of_name);
1495 u32 percent = stoi(sf.next(":"));
1496 std::string filename = sf.next(":");
1497 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1500 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1501 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1504 core::dimension2d<u32> dim = img->getDimension();
1505 core::position2d<s32> pos_base(0, 0);
1506 video::IImage *img2 =
1507 driver->createImage(video::ECF_A8R8G8B8, dim);
1510 core::position2d<s32> clippos(0, 0);
1511 clippos.Y = dim.Height * (100-percent) / 100;
1512 core::dimension2d<u32> clipdim = dim;
1513 clipdim.Height = clipdim.Height * percent / 100 + 1;
1514 core::rect<s32> cliprect(clippos, clipdim);
1515 img2->copyToWithAlpha(baseimg, pos_base,
1516 core::rect<s32>(v2s32(0,0), dim),
1517 video::SColor(255,255,255,255),
1524 Crops a frame of a vertical animation.
1525 N = frame count, I = frame index
1527 else if(part_of_name.substr(0,15) == "[verticalframe:")
1529 Strfnd sf(part_of_name);
1531 u32 frame_count = stoi(sf.next(":"));
1532 u32 frame_index = stoi(sf.next(":"));
1534 if(baseimg == NULL){
1535 errorstream<<"generateImagePart(): baseimg != NULL "
1536 <<"for part_of_name=\""<<part_of_name
1537 <<"\", cancelling."<<std::endl;
1541 v2u32 frame_size = baseimg->getDimension();
1542 frame_size.Y /= frame_count;
1544 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1547 errorstream<<"generateImagePart(): Could not create image "
1548 <<"for part_of_name=\""<<part_of_name
1549 <<"\", cancelling."<<std::endl;
1553 // Fill target image with transparency
1554 img->fill(video::SColor(0,0,0,0));
1556 core::dimension2d<u32> dim = frame_size;
1557 core::position2d<s32> pos_dst(0, 0);
1558 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1559 baseimg->copyToWithAlpha(img, pos_dst,
1560 core::rect<s32>(pos_src, dim),
1561 video::SColor(255,255,255,255),
1569 Applies a mask to an image
1571 else if(part_of_name.substr(0,6) == "[mask:")
1573 if (baseimg == NULL) {
1574 errorstream << "generateImage(): baseimg == NULL "
1575 << "for part_of_name=\"" << part_of_name
1576 << "\", cancelling." << std::endl;
1579 Strfnd sf(part_of_name);
1581 std::string filename = sf.next(":");
1583 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1585 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1586 img->getDimension());
1588 errorstream << "generateImage(): Failed to load \""
1589 << filename << "\".";
1594 Overlays image with given color
1595 color = color as ColorString
1597 else if (part_of_name.substr(0,10) == "[colorize:") {
1598 Strfnd sf(part_of_name);
1600 std::string color_str = sf.next(":");
1602 if (baseimg == NULL) {
1603 errorstream << "generateImagePart(): baseimg != NULL "
1604 << "for part_of_name=\"" << part_of_name
1605 << "\", cancelling." << std::endl;
1609 video::SColor color;
1610 if (!parseColorString(color_str, color, false))
1613 core::dimension2d<u32> dim = baseimg->getDimension();
1614 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1617 errorstream << "generateImagePart(): Could not create image "
1618 << "for part_of_name=\"" << part_of_name
1619 << "\", cancelling." << std::endl;
1623 img->fill(video::SColor(color));
1624 // Overlay the colored image
1625 blit_with_alpha_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim);
1630 errorstream << "generateImagePart(): Invalid "
1631 " modification: \"" << part_of_name << "\"" << std::endl;
1639 Draw an image on top of an another one, using the alpha channel of the
1642 This exists because IImage::copyToWithAlpha() doesn't seem to always
1645 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1646 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1648 for(u32 y0=0; y0<size.Y; y0++)
1649 for(u32 x0=0; x0<size.X; x0++)
1651 s32 src_x = src_pos.X + x0;
1652 s32 src_y = src_pos.Y + y0;
1653 s32 dst_x = dst_pos.X + x0;
1654 s32 dst_y = dst_pos.Y + y0;
1655 video::SColor src_c = src->getPixel(src_x, src_y);
1656 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1657 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1658 dst->setPixel(dst_x, dst_y, dst_c);
1663 Draw an image on top of an another one, using the alpha channel of the
1664 source image; only modify fully opaque pixels in destinaion
1666 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1667 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1669 for(u32 y0=0; y0<size.Y; y0++)
1670 for(u32 x0=0; x0<size.X; x0++)
1672 s32 src_x = src_pos.X + x0;
1673 s32 src_y = src_pos.Y + y0;
1674 s32 dst_x = dst_pos.X + x0;
1675 s32 dst_y = dst_pos.Y + y0;
1676 video::SColor src_c = src->getPixel(src_x, src_y);
1677 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1678 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1680 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1681 dst->setPixel(dst_x, dst_y, dst_c);
1687 Apply mask to destination
1689 static void apply_mask(video::IImage *mask, video::IImage *dst,
1690 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1692 for(u32 y0 = 0; y0 < size.Y; y0++) {
1693 for(u32 x0 = 0; x0 < size.X; x0++) {
1694 s32 mask_x = x0 + mask_pos.X;
1695 s32 mask_y = y0 + mask_pos.Y;
1696 s32 dst_x = x0 + dst_pos.X;
1697 s32 dst_y = y0 + dst_pos.Y;
1698 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1699 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1700 dst_c.color &= mask_c.color;
1701 dst->setPixel(dst_x, dst_y, dst_c);
1706 static void draw_crack(video::IImage *crack, video::IImage *dst,
1707 bool use_overlay, s32 frame_count, s32 progression,
1708 video::IVideoDriver *driver)
1710 // Dimension of destination image
1711 core::dimension2d<u32> dim_dst = dst->getDimension();
1712 // Dimension of original image
1713 core::dimension2d<u32> dim_crack = crack->getDimension();
1714 // Count of crack stages
1715 s32 crack_count = dim_crack.Height / dim_crack.Width;
1716 // Limit frame_count
1717 if(frame_count > (s32) dim_dst.Height)
1718 frame_count = dim_dst.Height;
1721 // Limit progression
1722 if(progression > crack_count-1)
1723 progression = crack_count-1;
1724 // Dimension of a single crack stage
1725 core::dimension2d<u32> dim_crack_cropped(
1729 // Dimension of the scaled crack stage,
1730 // which is the same as the dimension of a single destination frame
1731 core::dimension2d<u32> dim_crack_scaled(
1733 dim_dst.Height / frame_count
1735 // Create cropped and scaled crack images
1736 video::IImage *crack_cropped = driver->createImage(
1737 video::ECF_A8R8G8B8, dim_crack_cropped);
1738 video::IImage *crack_scaled = driver->createImage(
1739 video::ECF_A8R8G8B8, dim_crack_scaled);
1741 if(crack_cropped && crack_scaled)
1744 v2s32 pos_crack(0, progression*dim_crack.Width);
1745 crack->copyTo(crack_cropped,
1747 core::rect<s32>(pos_crack, dim_crack_cropped));
1748 // Scale crack image by copying
1749 crack_cropped->copyToScaling(crack_scaled);
1750 // Copy or overlay crack image onto each frame
1751 for(s32 i = 0; i < frame_count; ++i)
1753 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1756 blit_with_alpha_overlay(crack_scaled, dst,
1757 v2s32(0,0), dst_pos,
1762 blit_with_alpha(crack_scaled, dst,
1763 v2s32(0,0), dst_pos,
1770 crack_scaled->drop();
1773 crack_cropped->drop();
1776 void brighten(video::IImage *image)
1781 core::dimension2d<u32> dim = image->getDimension();
1783 for(u32 y=0; y<dim.Height; y++)
1784 for(u32 x=0; x<dim.Width; x++)
1786 video::SColor c = image->getPixel(x,y);
1787 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1788 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1789 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1790 image->setPixel(x,y,c);
1794 u32 parseImageTransform(const std::string& s)
1796 int total_transform = 0;
1798 std::string transform_names[8];
1799 transform_names[0] = "i";
1800 transform_names[1] = "r90";
1801 transform_names[2] = "r180";
1802 transform_names[3] = "r270";
1803 transform_names[4] = "fx";
1804 transform_names[6] = "fy";
1806 std::size_t pos = 0;
1807 while(pos < s.size())
1810 for(int i = 0; i <= 7; ++i)
1812 const std::string &name_i = transform_names[i];
1814 if(s[pos] == ('0' + i))
1820 else if(!(name_i.empty()) &&
1821 lowercase(s.substr(pos, name_i.size())) == name_i)
1824 pos += name_i.size();
1831 // Multiply total_transform and transform in the group D4
1834 new_total = (transform + total_transform) % 4;
1836 new_total = (transform - total_transform + 8) % 4;
1837 if((transform >= 4) ^ (total_transform >= 4))
1840 total_transform = new_total;
1842 return total_transform;
1845 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1847 if(transform % 2 == 0)
1850 return core::dimension2d<u32>(dim.Height, dim.Width);
1853 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1855 if(src == NULL || dst == NULL)
1858 core::dimension2d<u32> srcdim = src->getDimension();
1859 core::dimension2d<u32> dstdim = dst->getDimension();
1861 assert(dstdim == imageTransformDimension(transform, srcdim));
1862 assert(transform <= 7);
1865 Compute the transformation from source coordinates (sx,sy)
1866 to destination coordinates (dx,dy).
1870 if(transform == 0) // identity
1871 sxn = 0, syn = 2; // sx = dx, sy = dy
1872 else if(transform == 1) // rotate by 90 degrees ccw
1873 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1874 else if(transform == 2) // rotate by 180 degrees
1875 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1876 else if(transform == 3) // rotate by 270 degrees ccw
1877 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1878 else if(transform == 4) // flip x
1879 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1880 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1881 sxn = 2, syn = 0; // sx = dy, sy = dx
1882 else if(transform == 6) // flip y
1883 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1884 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1885 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1887 for(u32 dy=0; dy<dstdim.Height; dy++)
1888 for(u32 dx=0; dx<dstdim.Width; dx++)
1890 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1891 u32 sx = entries[sxn];
1892 u32 sy = entries[syn];
1893 video::SColor c = src->getPixel(sx,sy);
1894 dst->setPixel(dx,dy,c);
1898 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1901 if (isKnownSourceImage("override_normal.png"))
1902 return getTexture("override_normal.png", &id);
1903 std::string fname_base = name;
1904 std::string normal_ext = "_normal.png";
1905 size_t pos = fname_base.find(".");
1906 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1907 if (isKnownSourceImage(fname_normal)) {
1908 // look for image extension and replace it
1910 while ((i = fname_base.find(".", i)) != std::string::npos) {
1911 fname_base.replace(i, 4, normal_ext);
1912 i += normal_ext.length();
1914 return getTexture(fname_base, &id);