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
185 /* Upscale textures to user's requested minimum size. This is a trick to make
186 * filters look as good on low-res textures as on high-res ones, by making
187 * low-res textures BECOME high-res ones. This is helpful for worlds that
188 * mix high- and low-res textures, or for mods with least-common-denominator
189 * textures that don't have the resources to offer high-res alternatives.
191 video::IImage *textureMinSizeUpscale(video::IVideoDriver *driver, video::IImage *orig) {
194 s32 scaleto = g_settings->getS32("texture_min_size");
197 /* Calculate scaling needed to make the shortest texture dimension
198 * equal to the target minimum. If e.g. this is a vertical frames
199 * animation, the short dimension will be the real size.
201 const core::dimension2d<u32> dim = orig->getDimension();
202 u32 xscale = scaleto / dim.Width;
203 u32 yscale = scaleto / dim.Height;
204 u32 scale = (xscale > yscale) ? xscale : yscale;
206 // Never downscale; only scale up by 2x or more.
208 u32 w = scale * dim.Width;
209 u32 h = scale * dim.Height;
210 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
211 video::IImage *newimg = driver->createImage(
212 orig->getColorFormat(), newdim);
213 orig->copyToScaling(newimg);
222 SourceImageCache: A cache used for storing source images.
225 class SourceImageCache
228 ~SourceImageCache() {
229 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
230 iter != m_images.end(); iter++) {
231 iter->second->drop();
235 void insert(const std::string &name, video::IImage *img,
236 bool prefer_local, video::IVideoDriver *driver)
238 assert(img); // Pre-condition
240 std::map<std::string, video::IImage*>::iterator n;
241 n = m_images.find(name);
242 if (n != m_images.end()){
247 video::IImage* toadd = img;
248 bool need_to_grab = true;
250 // Try to use local texture instead if asked to
252 std::string path = getTexturePath(name);
254 video::IImage *img2 = driver->createImageFromFile(path.c_str());
257 need_to_grab = false;
262 /* Apply the "clean transparent" filter to textures, removing borders on transparent textures.
263 * PNG optimizers discard RGB values of fully-transparent pixels, but filters may expose the
264 * replacement colors at borders by blending to them; this filter compensates for that by
265 * filling in those RGB values from nearby pixels.
267 if (g_settings->getBool("texture_clean_transparent")) {
268 const core::dimension2d<u32> dim = toadd->getDimension();
270 // Walk each pixel looking for ones that will show as transparent.
271 for (u32 ctrx = 0; ctrx < dim.Width; ctrx++)
272 for (u32 ctry = 0; ctry < dim.Height; ctry++) {
273 irr::video::SColor c = toadd->getPixel(ctrx, ctry);
274 if (c.getAlpha() > 127)
277 // Sample size and total weighted r, g, b values.
278 u32 ss = 0, sr = 0, sg = 0, sb = 0;
280 // Walk each neighbor pixel (clipped to image bounds).
281 for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
282 sx <= (ctrx + 1) && sx < dim.Width; sx++)
283 for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
284 sy <= (ctry + 1) && sy < dim.Height; sy++) {
286 // Ignore the center pixel (its RGB is already
287 // presumed meaningless).
288 if ((sx == ctrx) && (sy == ctry))
291 // Ignore other nearby pixels that would be
292 // transparent upon display.
293 irr::video::SColor d = toadd->getPixel(sx, sy);
294 if(d.getAlpha() < 128)
297 // Add one weighted sample.
304 // If we found any neighbor RGB data, set pixel to average
305 // weighted by alpha.
310 toadd->setPixel(ctrx, ctry, c);
317 m_images[name] = toadd;
319 video::IImage* get(const std::string &name)
321 std::map<std::string, video::IImage*>::iterator n;
322 n = m_images.find(name);
323 if (n != m_images.end())
327 // Primarily fetches from cache, secondarily tries to read from filesystem
328 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
330 std::map<std::string, video::IImage*>::iterator n;
331 n = m_images.find(name);
332 if (n != m_images.end()){
333 n->second->grab(); // Grab for caller
336 video::IVideoDriver* driver = device->getVideoDriver();
337 std::string path = getTexturePath(name);
339 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
340 <<name<<"\""<<std::endl;
343 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
345 video::IImage *img = driver->createImageFromFile(path.c_str());
348 m_images[name] = img;
349 img->grab(); // Grab for caller
354 std::map<std::string, video::IImage*> m_images;
361 class TextureSource : public IWritableTextureSource
364 TextureSource(IrrlichtDevice *device);
365 virtual ~TextureSource();
369 Now, assume a texture with the id 1 exists, and has the name
370 "stone.png^mineral1".
371 Then a random thread calls getTextureId for a texture called
372 "stone.png^mineral1^crack0".
373 ...Now, WTF should happen? Well:
374 - getTextureId strips off stuff recursively from the end until
375 the remaining part is found, or nothing is left when
376 something is stripped out
378 But it is slow to search for textures by names and modify them
380 - ContentFeatures is made to contain ids for the basic plain
382 - Crack textures can be slow by themselves, but the framework
386 - Assume a texture with the id 1 exists, and has the name
387 "stone.png^mineral_coal.png".
388 - Now getNodeTile() stumbles upon a node which uses
389 texture id 1, and determines that MATERIAL_FLAG_CRACK
390 must be applied to the tile
391 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
392 has received the current crack level 0 from the client. It
393 finds out the name of the texture with getTextureName(1),
394 appends "^crack0" to it and gets a new texture id with
395 getTextureId("stone.png^mineral_coal.png^crack0").
400 Gets a texture id from cache or
401 - if main thread, generates the texture, adds to cache and returns id.
402 - if other thread, adds to request queue and waits for main thread.
404 The id 0 points to a NULL texture. It is returned in case of error.
406 u32 getTextureId(const std::string &name);
408 // Finds out the name of a cached texture.
409 std::string getTextureName(u32 id);
412 If texture specified by the name pointed by the id doesn't
413 exist, create it, then return the cached texture.
415 Can be called from any thread. If called from some other thread
416 and not found in cache, the call is queued to the main thread
419 video::ITexture* getTexture(u32 id);
421 video::ITexture* getTexture(const std::string &name, u32 *id);
423 // Returns a pointer to the irrlicht device
424 virtual IrrlichtDevice* getDevice()
429 bool isKnownSourceImage(const std::string &name)
431 bool is_known = false;
432 bool cache_found = m_source_image_existence.get(name, &is_known);
435 // Not found in cache; find out if a local file exists
436 is_known = (getTexturePath(name) != "");
437 m_source_image_existence.set(name, is_known);
441 // Processes queued texture requests from other threads.
442 // Shall be called from the main thread.
445 // Insert an image into the cache without touching the filesystem.
446 // Shall be called from the main thread.
447 void insertSourceImage(const std::string &name, video::IImage *img);
449 // Rebuild images and textures from the current set of source images
450 // Shall be called from the main thread.
451 void rebuildImagesAndTextures();
453 // Render a mesh to a texture.
454 // Returns NULL if render-to-texture failed.
455 // Shall be called from the main thread.
456 video::ITexture* generateTextureFromMesh(
457 const TextureFromMeshParams ¶ms);
459 // Generates an image from a full string like
460 // "stone.png^mineral_coal.png^[crack:1:0".
461 // Shall be called from the main thread.
462 video::IImage* generateImage(const std::string &name);
464 video::ITexture* getNormalTexture(const std::string &name);
467 // The id of the thread that is allowed to use irrlicht directly
468 threadid_t m_main_thread;
469 // The irrlicht device
470 IrrlichtDevice *m_device;
472 // Cache of source images
473 // This should be only accessed from the main thread
474 SourceImageCache m_sourcecache;
476 // Generate a texture
477 u32 generateTexture(const std::string &name);
479 // Generate image based on a string like "stone.png" or "[crack:1:0".
480 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
481 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
483 // Thread-safe cache of what source images are known (true = known)
484 MutexedMap<std::string, bool> m_source_image_existence;
486 // A texture id is index in this array.
487 // The first position contains a NULL texture.
488 std::vector<TextureInfo> m_textureinfo_cache;
489 // Maps a texture name to an index in the former.
490 std::map<std::string, u32> m_name_to_id;
491 // The two former containers are behind this mutex
492 JMutex m_textureinfo_cache_mutex;
494 // Queued texture fetches (to be processed by the main thread)
495 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
497 // Textures that have been overwritten with other ones
498 // but can't be deleted because the ITexture* might still be used
499 std::vector<video::ITexture*> m_texture_trash;
501 // Cached settings needed for making textures from meshes
502 bool m_setting_trilinear_filter;
503 bool m_setting_bilinear_filter;
504 bool m_setting_anisotropic_filter;
507 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
509 return new TextureSource(device);
512 TextureSource::TextureSource(IrrlichtDevice *device):
515 assert(m_device); // Pre-condition
517 m_main_thread = get_current_thread_id();
519 // Add a NULL TextureInfo as the first index, named ""
520 m_textureinfo_cache.push_back(TextureInfo(""));
521 m_name_to_id[""] = 0;
523 // Cache some settings
524 // Note: Since this is only done once, the game must be restarted
525 // for these settings to take effect
526 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
527 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
528 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
531 TextureSource::~TextureSource()
533 video::IVideoDriver* driver = m_device->getVideoDriver();
535 unsigned int textures_before = driver->getTextureCount();
537 for (std::vector<TextureInfo>::iterator iter =
538 m_textureinfo_cache.begin();
539 iter != m_textureinfo_cache.end(); iter++)
543 driver->removeTexture(iter->texture);
545 m_textureinfo_cache.clear();
547 for (std::vector<video::ITexture*>::iterator iter =
548 m_texture_trash.begin(); iter != m_texture_trash.end();
550 video::ITexture *t = *iter;
552 //cleanup trashed texture
553 driver->removeTexture(t);
556 infostream << "~TextureSource() "<< textures_before << "/"
557 << driver->getTextureCount() << std::endl;
560 u32 TextureSource::getTextureId(const std::string &name)
562 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
566 See if texture already exists
568 JMutexAutoLock lock(m_textureinfo_cache_mutex);
569 std::map<std::string, u32>::iterator n;
570 n = m_name_to_id.find(name);
571 if (n != m_name_to_id.end())
580 if (get_current_thread_id() == m_main_thread)
582 return generateTexture(name);
586 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
588 // We're gonna ask the result to be put into here
589 static ResultQueue<std::string, u32, u8, u8> result_queue;
591 // Throw a request in
592 m_get_texture_queue.add(name, 0, 0, &result_queue);
594 /*infostream<<"Waiting for texture from main thread, name=\""
595 <<name<<"\""<<std::endl;*/
600 // Wait result for a second
601 GetResult<std::string, u32, u8, u8>
602 result = result_queue.pop_front(1000);
604 if (result.key == name) {
609 catch(ItemNotFoundException &e)
611 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
616 infostream<<"getTextureId(): Failed"<<std::endl;
621 // Draw an image on top of an another one, using the alpha channel of the
623 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
624 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
626 // Like blit_with_alpha, but only modifies destination pixels that
628 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
629 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
631 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
632 // and modifies any destination pixels that are not fully transparent
633 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
634 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
636 // Apply a mask to an image
637 static void apply_mask(video::IImage *mask, video::IImage *dst,
638 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
640 // Draw or overlay a crack
641 static void draw_crack(video::IImage *crack, video::IImage *dst,
642 bool use_overlay, s32 frame_count, s32 progression,
643 video::IVideoDriver *driver);
646 void brighten(video::IImage *image);
647 // Parse a transform name
648 u32 parseImageTransform(const std::string& s);
649 // Apply transform to image dimension
650 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
651 // Apply transform to image data
652 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
655 This method generates all the textures
657 u32 TextureSource::generateTexture(const std::string &name)
659 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
661 // Empty name means texture 0
663 infostream<<"generateTexture(): name is empty"<<std::endl;
669 See if texture already exists
671 JMutexAutoLock lock(m_textureinfo_cache_mutex);
672 std::map<std::string, u32>::iterator n;
673 n = m_name_to_id.find(name);
674 if (n != m_name_to_id.end()) {
680 Calling only allowed from main thread
682 if (get_current_thread_id() != m_main_thread) {
683 errorstream<<"TextureSource::generateTexture() "
684 "called not from main thread"<<std::endl;
688 video::IVideoDriver *driver = m_device->getVideoDriver();
689 sanity_check(driver);
691 video::IImage *origimg = generateImage(name);
692 video::IImage *img = textureMinSizeUpscale(driver, origimg);
694 video::ITexture *tex = NULL;
698 img = Align2Npot2(img, driver);
700 // Create texture from resulting image
701 tex = driver->addTexture(name.c_str(), img);
703 if((origimg != NULL) && (img != origimg))
708 Add texture to caches (add NULL textures too)
711 JMutexAutoLock lock(m_textureinfo_cache_mutex);
713 u32 id = m_textureinfo_cache.size();
714 TextureInfo ti(name, tex);
715 m_textureinfo_cache.push_back(ti);
716 m_name_to_id[name] = id;
721 std::string TextureSource::getTextureName(u32 id)
723 JMutexAutoLock lock(m_textureinfo_cache_mutex);
725 if (id >= m_textureinfo_cache.size())
727 errorstream<<"TextureSource::getTextureName(): id="<<id
728 <<" >= m_textureinfo_cache.size()="
729 <<m_textureinfo_cache.size()<<std::endl;
733 return m_textureinfo_cache[id].name;
736 video::ITexture* TextureSource::getTexture(u32 id)
738 JMutexAutoLock lock(m_textureinfo_cache_mutex);
740 if (id >= m_textureinfo_cache.size())
743 return m_textureinfo_cache[id].texture;
746 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
748 u32 actual_id = getTextureId(name);
752 return getTexture(actual_id);
755 void TextureSource::processQueue()
760 //NOTE this is only thread safe for ONE consumer thread!
761 if (!m_get_texture_queue.empty())
763 GetRequest<std::string, u32, u8, u8>
764 request = m_get_texture_queue.pop();
766 /*infostream<<"TextureSource::processQueue(): "
767 <<"got texture request with "
768 <<"name=\""<<request.key<<"\""
771 m_get_texture_queue.pushResult(request, generateTexture(request.key));
775 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
777 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
779 sanity_check(get_current_thread_id() == m_main_thread);
781 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
782 m_source_image_existence.set(name, true);
785 void TextureSource::rebuildImagesAndTextures()
787 JMutexAutoLock lock(m_textureinfo_cache_mutex);
789 video::IVideoDriver* driver = m_device->getVideoDriver();
790 sanity_check(driver);
793 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
794 TextureInfo *ti = &m_textureinfo_cache[i];
795 video::IImage *origimg = generateImage(ti->name);
796 video::IImage *img = textureMinSizeUpscale(driver, origimg);
798 img = Align2Npot2(img, driver);
799 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
800 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
802 // Create texture from resulting image
803 video::ITexture *t = NULL;
805 t = driver->addTexture(ti->name.c_str(), img);
807 if(origimg && (origimg != img))
810 video::ITexture *t_old = ti->texture;
815 m_texture_trash.push_back(t_old);
819 video::ITexture* TextureSource::generateTextureFromMesh(
820 const TextureFromMeshParams ¶ms)
822 video::IVideoDriver *driver = m_device->getVideoDriver();
823 sanity_check(driver);
826 const GLubyte* renderstr = glGetString(GL_RENDERER);
827 std::string renderer((char*) renderstr);
829 // use no render to texture hack
831 (renderer.find("Adreno") != std::string::npos) ||
832 (renderer.find("Mali") != std::string::npos) ||
833 (renderer.find("Immersion") != std::string::npos) ||
834 (renderer.find("Tegra") != std::string::npos) ||
835 g_settings->getBool("inventory_image_hack")
837 // Get a scene manager
838 scene::ISceneManager *smgr_main = m_device->getSceneManager();
839 sanity_check(smgr_main);
840 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
843 const float scaling = 0.2;
845 scene::IMeshSceneNode* meshnode =
846 smgr->addMeshSceneNode(params.mesh, NULL,
847 -1, v3f(0,0,0), v3f(0,0,0),
848 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
849 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
850 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
851 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
852 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
853 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
855 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
856 params.camera_position, params.camera_lookat);
857 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
858 camera->setProjectionMatrix(params.camera_projection_matrix, false);
860 smgr->setAmbientLight(params.ambient_light);
861 smgr->addLightSceneNode(0,
862 params.light_position,
864 params.light_radius*scaling);
866 core::dimension2d<u32> screen = driver->getScreenSize();
869 driver->beginScene(true, true, video::SColor(0,0,0,0));
870 driver->clearZBuffer();
873 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
875 irr::video::IImage* rawImage =
876 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
878 u8* pixels = static_cast<u8*>(rawImage->lock());
885 core::rect<s32> source(
886 screen.Width /2 - (screen.Width * (scaling / 2)),
887 screen.Height/2 - (screen.Height * (scaling / 2)),
888 screen.Width /2 + (screen.Width * (scaling / 2)),
889 screen.Height/2 + (screen.Height * (scaling / 2))
892 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
893 partsize.Width, partsize.Height, GL_RGBA,
894 GL_UNSIGNED_BYTE, pixels);
898 // Drop scene manager
901 unsigned int pixelcount = partsize.Width*partsize.Height;
904 for (unsigned int i=0; i < pixelcount; i++) {
922 video::IImage* inventory_image =
923 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
925 rawImage->copyToScaling(inventory_image);
928 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
929 inventory_image->drop();
932 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
936 driver->makeColorKeyTexture(rtt, v2s32(0,0));
938 if (params.delete_texture_on_shutdown)
939 m_texture_trash.push_back(rtt);
945 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
947 static bool warned = false;
950 errorstream<<"TextureSource::generateTextureFromMesh(): "
951 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
957 // Create render target texture
958 video::ITexture *rtt = driver->addRenderTargetTexture(
959 params.dim, params.rtt_texture_name.c_str(),
960 video::ECF_A8R8G8B8);
963 errorstream<<"TextureSource::generateTextureFromMesh(): "
964 <<"addRenderTargetTexture returned NULL."<<std::endl;
969 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
970 driver->removeTexture(rtt);
971 errorstream<<"TextureSource::generateTextureFromMesh(): "
972 <<"failed to set render target"<<std::endl;
976 // Get a scene manager
977 scene::ISceneManager *smgr_main = m_device->getSceneManager();
979 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
982 scene::IMeshSceneNode* meshnode =
983 smgr->addMeshSceneNode(params.mesh, NULL,
984 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
985 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
986 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
987 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
988 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
989 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
991 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
992 params.camera_position, params.camera_lookat);
993 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
994 camera->setProjectionMatrix(params.camera_projection_matrix, false);
996 smgr->setAmbientLight(params.ambient_light);
997 smgr->addLightSceneNode(0,
998 params.light_position,
1000 params.light_radius);
1003 driver->beginScene(true, true, video::SColor(0,0,0,0));
1007 // Drop scene manager
1010 // Unset render target
1011 driver->setRenderTarget(0, false, true, 0);
1013 if (params.delete_texture_on_shutdown)
1014 m_texture_trash.push_back(rtt);
1019 video::IImage* TextureSource::generateImage(const std::string &name)
1025 const char separator = '^';
1026 const char paren_open = '(';
1027 const char paren_close = ')';
1029 // Find last separator in the name
1030 s32 last_separator_pos = -1;
1032 for (s32 i = name.size() - 1; i >= 0; i--) {
1035 if (paren_bal == 0) {
1036 last_separator_pos = i;
1037 i = -1; // break out of loop
1041 if (paren_bal == 0) {
1042 errorstream << "generateImage(): unbalanced parentheses"
1043 << "(extranous '(') while generating texture \""
1044 << name << "\"" << std::endl;
1056 if (paren_bal > 0) {
1057 errorstream << "generateImage(): unbalanced parentheses"
1058 << "(missing matching '(') while generating texture \""
1059 << name << "\"" << std::endl;
1064 video::IImage *baseimg = NULL;
1067 If separator was found, make the base image
1068 using a recursive call.
1070 if (last_separator_pos != -1) {
1071 baseimg = generateImage(name.substr(0, last_separator_pos));
1075 video::IVideoDriver* driver = m_device->getVideoDriver();
1076 sanity_check(driver);
1079 Parse out the last part of the name of the image and act
1083 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1086 If this name is enclosed in parentheses, generate it
1087 and blit it onto the base image
1089 if (last_part_of_name[0] == paren_open
1090 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1091 std::string name2 = last_part_of_name.substr(1,
1092 last_part_of_name.size() - 2);
1093 video::IImage *tmp = generateImage(name2);
1095 errorstream << "generateImage(): "
1096 "Failed to generate \"" << name2 << "\""
1100 core::dimension2d<u32> dim = tmp->getDimension();
1102 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1103 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1105 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1106 // Generate image according to part of name
1107 errorstream << "generateImage(): "
1108 "Failed to generate \"" << last_part_of_name << "\""
1112 // If no resulting image, print a warning
1113 if (baseimg == NULL) {
1114 errorstream << "generateImage(): baseimg is NULL (attempted to"
1115 " create texture \"" << name << "\")" << std::endl;
1122 #include <GLES/gl.h>
1124 * Check and align image to npot2 if required by hardware
1125 * @param image image to check for npot2 alignment
1126 * @param driver driver to use for image operations
1127 * @return image or copy of image aligned to npot2
1129 video::IImage * Align2Npot2(video::IImage * image,
1130 video::IVideoDriver* driver)
1132 if (image == NULL) {
1136 core::dimension2d<u32> dim = image->getDimension();
1138 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1139 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1143 unsigned int height = npot2(dim.Height);
1144 unsigned int width = npot2(dim.Width);
1146 if ((dim.Height == height) &&
1147 (dim.Width == width)) {
1151 if (dim.Height > height) {
1155 if (dim.Width > width) {
1159 video::IImage *targetimage =
1160 driver->createImage(video::ECF_A8R8G8B8,
1161 core::dimension2d<u32>(width, height));
1163 if (targetimage != NULL) {
1164 image->copyToScaling(targetimage);
1172 bool TextureSource::generateImagePart(std::string part_of_name,
1173 video::IImage *& baseimg)
1175 video::IVideoDriver* driver = m_device->getVideoDriver();
1176 sanity_check(driver);
1178 // Stuff starting with [ are special commands
1179 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1181 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1183 image = Align2Npot2(image, driver);
1185 if (image == NULL) {
1186 if (part_of_name != "") {
1187 if (part_of_name.find("_normal.png") == std::string::npos){
1188 errorstream<<"generateImage(): Could not load image \""
1189 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1190 errorstream<<"generateImage(): Creating a dummy"
1191 <<" image for \""<<part_of_name<<"\""<<std::endl;
1193 infostream<<"generateImage(): Could not load normal map \""
1194 <<part_of_name<<"\""<<std::endl;
1195 infostream<<"generateImage(): Creating a dummy"
1196 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1200 // Just create a dummy image
1201 //core::dimension2d<u32> dim(2,2);
1202 core::dimension2d<u32> dim(1,1);
1203 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1204 sanity_check(image != NULL);
1205 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1206 image->setPixel(1,0, video::SColor(255,0,255,0));
1207 image->setPixel(0,1, video::SColor(255,0,0,255));
1208 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1209 image->setPixel(0,0, video::SColor(255,myrand()%256,
1210 myrand()%256,myrand()%256));
1211 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1212 myrand()%256,myrand()%256));
1213 image->setPixel(0,1, video::SColor(255,myrand()%256,
1214 myrand()%256,myrand()%256));
1215 image->setPixel(1,1, video::SColor(255,myrand()%256,
1216 myrand()%256,myrand()%256));*/
1219 // If base image is NULL, load as base.
1220 if (baseimg == NULL)
1222 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1224 Copy it this way to get an alpha channel.
1225 Otherwise images with alpha cannot be blitted on
1226 images that don't have alpha in the original file.
1228 core::dimension2d<u32> dim = image->getDimension();
1229 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1230 image->copyTo(baseimg);
1232 // Else blit on base.
1235 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1236 // Size of the copied area
1237 core::dimension2d<u32> dim = image->getDimension();
1238 //core::dimension2d<u32> dim(16,16);
1239 // Position to copy the blitted to in the base image
1240 core::position2d<s32> pos_to(0,0);
1241 // Position to copy the blitted from in the blitted image
1242 core::position2d<s32> pos_from(0,0);
1244 /*image->copyToWithAlpha(baseimg, pos_to,
1245 core::rect<s32>(pos_from, dim),
1246 video::SColor(255,255,255,255),
1248 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1255 // A special texture modification
1257 /*infostream<<"generateImage(): generating special "
1258 <<"modification \""<<part_of_name<<"\""
1264 Adds a cracking texture
1265 N = animation frame count, P = crack progression
1267 if (part_of_name.substr(0,6) == "[crack")
1269 if (baseimg == NULL) {
1270 errorstream<<"generateImagePart(): baseimg == NULL "
1271 <<"for part_of_name=\""<<part_of_name
1272 <<"\", cancelling."<<std::endl;
1276 // Crack image number and overlay option
1277 bool use_overlay = (part_of_name[6] == 'o');
1278 Strfnd sf(part_of_name);
1280 s32 frame_count = stoi(sf.next(":"));
1281 s32 progression = stoi(sf.next(":"));
1286 It is an image with a number of cracking stages
1289 video::IImage *img_crack = m_sourcecache.getOrLoad(
1290 "crack_anylength.png", m_device);
1292 if (img_crack && progression >= 0)
1294 draw_crack(img_crack, baseimg,
1295 use_overlay, frame_count,
1296 progression, driver);
1301 [combine:WxH:X,Y=filename:X,Y=filename2
1302 Creates a bigger texture from an amount of smaller ones
1304 else if (part_of_name.substr(0,8) == "[combine")
1306 Strfnd sf(part_of_name);
1308 u32 w0 = stoi(sf.next("x"));
1309 u32 h0 = stoi(sf.next(":"));
1310 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1311 core::dimension2d<u32> dim(w0,h0);
1312 if (baseimg == NULL) {
1313 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1314 baseimg->fill(video::SColor(0,0,0,0));
1316 while (sf.atend() == false) {
1317 u32 x = stoi(sf.next(","));
1318 u32 y = stoi(sf.next("="));
1319 std::string filename = sf.next(":");
1320 infostream<<"Adding \""<<filename
1321 <<"\" to combined ("<<x<<","<<y<<")"
1323 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1325 core::dimension2d<u32> dim = img->getDimension();
1326 infostream<<"Size "<<dim.Width
1327 <<"x"<<dim.Height<<std::endl;
1328 core::position2d<s32> pos_base(x, y);
1329 video::IImage *img2 =
1330 driver->createImage(video::ECF_A8R8G8B8, dim);
1333 /*img2->copyToWithAlpha(baseimg, pos_base,
1334 core::rect<s32>(v2s32(0,0), dim),
1335 video::SColor(255,255,255,255),
1337 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1340 errorstream << "generateImagePart(): Failed to load image \""
1341 << filename << "\" for [combine" << std::endl;
1348 else if (part_of_name.substr(0,9) == "[brighten")
1350 if (baseimg == NULL) {
1351 errorstream<<"generateImagePart(): baseimg==NULL "
1352 <<"for part_of_name=\""<<part_of_name
1353 <<"\", cancelling."<<std::endl;
1361 Make image completely opaque.
1362 Used for the leaves texture when in old leaves mode, so
1363 that the transparent parts don't look completely black
1364 when simple alpha channel is used for rendering.
1366 else if (part_of_name.substr(0,8) == "[noalpha")
1368 if (baseimg == NULL){
1369 errorstream<<"generateImagePart(): baseimg==NULL "
1370 <<"for part_of_name=\""<<part_of_name
1371 <<"\", cancelling."<<std::endl;
1375 core::dimension2d<u32> dim = baseimg->getDimension();
1377 // Set alpha to full
1378 for (u32 y=0; y<dim.Height; y++)
1379 for (u32 x=0; x<dim.Width; x++)
1381 video::SColor c = baseimg->getPixel(x,y);
1383 baseimg->setPixel(x,y,c);
1388 Convert one color to transparent.
1390 else if (part_of_name.substr(0,11) == "[makealpha:")
1392 if (baseimg == NULL) {
1393 errorstream<<"generateImagePart(): baseimg == NULL "
1394 <<"for part_of_name=\""<<part_of_name
1395 <<"\", cancelling."<<std::endl;
1399 Strfnd sf(part_of_name.substr(11));
1400 u32 r1 = stoi(sf.next(","));
1401 u32 g1 = stoi(sf.next(","));
1402 u32 b1 = stoi(sf.next(""));
1403 std::string filename = sf.next("");
1405 core::dimension2d<u32> dim = baseimg->getDimension();
1407 /*video::IImage *oldbaseimg = baseimg;
1408 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1409 oldbaseimg->copyTo(baseimg);
1410 oldbaseimg->drop();*/
1412 // Set alpha to full
1413 for (u32 y=0; y<dim.Height; y++)
1414 for (u32 x=0; x<dim.Width; x++)
1416 video::SColor c = baseimg->getPixel(x,y);
1418 u32 g = c.getGreen();
1419 u32 b = c.getBlue();
1420 if (!(r == r1 && g == g1 && b == b1))
1423 baseimg->setPixel(x,y,c);
1428 Rotates and/or flips the image.
1430 N can be a number (between 0 and 7) or a transform name.
1431 Rotations are counter-clockwise.
1433 1 R90 rotate by 90 degrees
1434 2 R180 rotate by 180 degrees
1435 3 R270 rotate by 270 degrees
1437 5 FXR90 flip X then rotate by 90 degrees
1439 7 FYR90 flip Y then rotate by 90 degrees
1441 Note: Transform names can be concatenated to produce
1442 their product (applies the first then the second).
1443 The resulting transform will be equivalent to one of the
1444 eight existing ones, though (see: dihedral group).
1446 else if (part_of_name.substr(0,10) == "[transform")
1448 if (baseimg == NULL) {
1449 errorstream<<"generateImagePart(): baseimg == NULL "
1450 <<"for part_of_name=\""<<part_of_name
1451 <<"\", cancelling."<<std::endl;
1455 u32 transform = parseImageTransform(part_of_name.substr(10));
1456 core::dimension2d<u32> dim = imageTransformDimension(
1457 transform, baseimg->getDimension());
1458 video::IImage *image = driver->createImage(
1459 baseimg->getColorFormat(), dim);
1460 sanity_check(image != NULL);
1461 imageTransform(transform, baseimg, image);
1466 [inventorycube{topimage{leftimage{rightimage
1467 In every subimage, replace ^ with &.
1468 Create an "inventory cube".
1469 NOTE: This should be used only on its own.
1470 Example (a grass block (not actually used in game):
1471 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1473 else if (part_of_name.substr(0,14) == "[inventorycube")
1475 if (baseimg != NULL){
1476 errorstream<<"generateImagePart(): baseimg != NULL "
1477 <<"for part_of_name=\""<<part_of_name
1478 <<"\", cancelling."<<std::endl;
1482 str_replace(part_of_name, '&', '^');
1483 Strfnd sf(part_of_name);
1485 std::string imagename_top = sf.next("{");
1486 std::string imagename_left = sf.next("{");
1487 std::string imagename_right = sf.next("{");
1489 // Generate images for the faces of the cube
1490 video::IImage *img_top = generateImage(imagename_top);
1491 video::IImage *img_left = generateImage(imagename_left);
1492 video::IImage *img_right = generateImage(imagename_right);
1494 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1495 errorstream << "generateImagePart(): Failed to create textures"
1496 << " for inventorycube \"" << part_of_name << "\""
1498 baseimg = generateImage(imagename_top);
1503 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1504 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1506 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1507 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1509 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1510 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1513 // Create textures from images
1514 video::ITexture *texture_top = driver->addTexture(
1515 (imagename_top + "__temp__").c_str(), img_top);
1516 video::ITexture *texture_left = driver->addTexture(
1517 (imagename_left + "__temp__").c_str(), img_left);
1518 video::ITexture *texture_right = driver->addTexture(
1519 (imagename_right + "__temp__").c_str(), img_right);
1520 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1528 Draw a cube mesh into a render target texture
1530 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1531 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1532 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1533 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1534 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1535 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1536 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1537 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1539 TextureFromMeshParams params;
1541 params.dim.set(64, 64);
1542 params.rtt_texture_name = part_of_name + "_RTT";
1543 // We will delete the rtt texture ourselves
1544 params.delete_texture_on_shutdown = false;
1545 params.camera_position.set(0, 1.0, -1.5);
1546 params.camera_position.rotateXZBy(45);
1547 params.camera_lookat.set(0, 0, 0);
1548 // Set orthogonal projection
1549 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1550 1.65, 1.65, 0, 100);
1552 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1553 params.light_position.set(10, 100, -50);
1554 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1555 params.light_radius = 1000;
1557 video::ITexture *rtt = generateTextureFromMesh(params);
1563 driver->removeTexture(texture_top);
1564 driver->removeTexture(texture_left);
1565 driver->removeTexture(texture_right);
1568 baseimg = generateImage(imagename_top);
1572 // Create image of render target
1573 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1574 FATAL_ERROR_IF(!image, "Could not create image of render target");
1577 driver->removeTexture(rtt);
1579 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1582 image->copyTo(baseimg);
1587 [lowpart:percent:filename
1588 Adds the lower part of a texture
1590 else if (part_of_name.substr(0,9) == "[lowpart:")
1592 Strfnd sf(part_of_name);
1594 u32 percent = stoi(sf.next(":"));
1595 std::string filename = sf.next(":");
1596 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1598 if (baseimg == NULL)
1599 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1600 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1603 core::dimension2d<u32> dim = img->getDimension();
1604 core::position2d<s32> pos_base(0, 0);
1605 video::IImage *img2 =
1606 driver->createImage(video::ECF_A8R8G8B8, dim);
1609 core::position2d<s32> clippos(0, 0);
1610 clippos.Y = dim.Height * (100-percent) / 100;
1611 core::dimension2d<u32> clipdim = dim;
1612 clipdim.Height = clipdim.Height * percent / 100 + 1;
1613 core::rect<s32> cliprect(clippos, clipdim);
1614 img2->copyToWithAlpha(baseimg, pos_base,
1615 core::rect<s32>(v2s32(0,0), dim),
1616 video::SColor(255,255,255,255),
1623 Crops a frame of a vertical animation.
1624 N = frame count, I = frame index
1626 else if (part_of_name.substr(0,15) == "[verticalframe:")
1628 Strfnd sf(part_of_name);
1630 u32 frame_count = stoi(sf.next(":"));
1631 u32 frame_index = stoi(sf.next(":"));
1633 if (baseimg == NULL){
1634 errorstream<<"generateImagePart(): baseimg != NULL "
1635 <<"for part_of_name=\""<<part_of_name
1636 <<"\", cancelling."<<std::endl;
1640 v2u32 frame_size = baseimg->getDimension();
1641 frame_size.Y /= frame_count;
1643 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1646 errorstream<<"generateImagePart(): Could not create image "
1647 <<"for part_of_name=\""<<part_of_name
1648 <<"\", cancelling."<<std::endl;
1652 // Fill target image with transparency
1653 img->fill(video::SColor(0,0,0,0));
1655 core::dimension2d<u32> dim = frame_size;
1656 core::position2d<s32> pos_dst(0, 0);
1657 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1658 baseimg->copyToWithAlpha(img, pos_dst,
1659 core::rect<s32>(pos_src, dim),
1660 video::SColor(255,255,255,255),
1668 Applies a mask to an image
1670 else if (part_of_name.substr(0,6) == "[mask:")
1672 if (baseimg == NULL) {
1673 errorstream << "generateImage(): baseimg == NULL "
1674 << "for part_of_name=\"" << part_of_name
1675 << "\", cancelling." << std::endl;
1678 Strfnd sf(part_of_name);
1680 std::string filename = sf.next(":");
1682 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1684 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1685 img->getDimension());
1687 errorstream << "generateImage(): Failed to load \""
1688 << filename << "\".";
1693 Overlays image with given color
1694 color = color as ColorString
1696 else if (part_of_name.substr(0,10) == "[colorize:") {
1697 Strfnd sf(part_of_name);
1699 std::string color_str = sf.next(":");
1700 std::string ratio_str = sf.next(":");
1702 if (baseimg == NULL) {
1703 errorstream << "generateImagePart(): baseimg != NULL "
1704 << "for part_of_name=\"" << part_of_name
1705 << "\", cancelling." << std::endl;
1709 video::SColor color;
1712 if (!parseColorString(color_str, color, false))
1715 if (is_number(ratio_str))
1716 ratio = mystoi(ratio_str, 0, 255);
1718 core::dimension2d<u32> dim = baseimg->getDimension();
1719 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1722 errorstream << "generateImagePart(): Could not create image "
1723 << "for part_of_name=\"" << part_of_name
1724 << "\", cancelling." << std::endl;
1728 img->fill(video::SColor(color));
1729 // Overlay the colored image
1730 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1735 errorstream << "generateImagePart(): Invalid "
1736 " modification: \"" << part_of_name << "\"" << std::endl;
1744 Draw an image on top of an another one, using the alpha channel of the
1747 This exists because IImage::copyToWithAlpha() doesn't seem to always
1750 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1751 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1753 for (u32 y0=0; y0<size.Y; y0++)
1754 for (u32 x0=0; x0<size.X; x0++)
1756 s32 src_x = src_pos.X + x0;
1757 s32 src_y = src_pos.Y + y0;
1758 s32 dst_x = dst_pos.X + x0;
1759 s32 dst_y = dst_pos.Y + y0;
1760 video::SColor src_c = src->getPixel(src_x, src_y);
1761 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1762 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1763 dst->setPixel(dst_x, dst_y, dst_c);
1768 Draw an image on top of an another one, using the alpha channel of the
1769 source image; only modify fully opaque pixels in destinaion
1771 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1772 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1774 for (u32 y0=0; y0<size.Y; y0++)
1775 for (u32 x0=0; x0<size.X; x0++)
1777 s32 src_x = src_pos.X + x0;
1778 s32 src_y = src_pos.Y + y0;
1779 s32 dst_x = dst_pos.X + x0;
1780 s32 dst_y = dst_pos.Y + y0;
1781 video::SColor src_c = src->getPixel(src_x, src_y);
1782 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1783 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1785 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1786 dst->setPixel(dst_x, dst_y, dst_c);
1792 Draw an image on top of an another one, using the specified ratio
1793 modify all partially-opaque pixels in the destination.
1795 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1796 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1798 for (u32 y0 = 0; y0 < size.Y; y0++)
1799 for (u32 x0 = 0; x0 < size.X; x0++)
1801 s32 src_x = src_pos.X + x0;
1802 s32 src_y = src_pos.Y + y0;
1803 s32 dst_x = dst_pos.X + x0;
1804 s32 dst_y = dst_pos.Y + y0;
1805 video::SColor src_c = src->getPixel(src_x, src_y);
1806 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1807 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1810 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1812 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1813 dst->setPixel(dst_x, dst_y, dst_c);
1819 Apply mask to destination
1821 static void apply_mask(video::IImage *mask, video::IImage *dst,
1822 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1824 for (u32 y0 = 0; y0 < size.Y; y0++) {
1825 for (u32 x0 = 0; x0 < size.X; x0++) {
1826 s32 mask_x = x0 + mask_pos.X;
1827 s32 mask_y = y0 + mask_pos.Y;
1828 s32 dst_x = x0 + dst_pos.X;
1829 s32 dst_y = y0 + dst_pos.Y;
1830 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1831 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1832 dst_c.color &= mask_c.color;
1833 dst->setPixel(dst_x, dst_y, dst_c);
1838 static void draw_crack(video::IImage *crack, video::IImage *dst,
1839 bool use_overlay, s32 frame_count, s32 progression,
1840 video::IVideoDriver *driver)
1842 // Dimension of destination image
1843 core::dimension2d<u32> dim_dst = dst->getDimension();
1844 // Dimension of original image
1845 core::dimension2d<u32> dim_crack = crack->getDimension();
1846 // Count of crack stages
1847 s32 crack_count = dim_crack.Height / dim_crack.Width;
1848 // Limit frame_count
1849 if (frame_count > (s32) dim_dst.Height)
1850 frame_count = dim_dst.Height;
1851 if (frame_count < 1)
1853 // Limit progression
1854 if (progression > crack_count-1)
1855 progression = crack_count-1;
1856 // Dimension of a single crack stage
1857 core::dimension2d<u32> dim_crack_cropped(
1861 // Dimension of the scaled crack stage,
1862 // which is the same as the dimension of a single destination frame
1863 core::dimension2d<u32> dim_crack_scaled(
1865 dim_dst.Height / frame_count
1867 // Create cropped and scaled crack images
1868 video::IImage *crack_cropped = driver->createImage(
1869 video::ECF_A8R8G8B8, dim_crack_cropped);
1870 video::IImage *crack_scaled = driver->createImage(
1871 video::ECF_A8R8G8B8, dim_crack_scaled);
1873 if (crack_cropped && crack_scaled)
1876 v2s32 pos_crack(0, progression*dim_crack.Width);
1877 crack->copyTo(crack_cropped,
1879 core::rect<s32>(pos_crack, dim_crack_cropped));
1880 // Scale crack image by copying
1881 crack_cropped->copyToScaling(crack_scaled);
1882 // Copy or overlay crack image onto each frame
1883 for (s32 i = 0; i < frame_count; ++i)
1885 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1888 blit_with_alpha_overlay(crack_scaled, dst,
1889 v2s32(0,0), dst_pos,
1894 blit_with_alpha(crack_scaled, dst,
1895 v2s32(0,0), dst_pos,
1902 crack_scaled->drop();
1905 crack_cropped->drop();
1908 void brighten(video::IImage *image)
1913 core::dimension2d<u32> dim = image->getDimension();
1915 for (u32 y=0; y<dim.Height; y++)
1916 for (u32 x=0; x<dim.Width; x++)
1918 video::SColor c = image->getPixel(x,y);
1919 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1920 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1921 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1922 image->setPixel(x,y,c);
1926 u32 parseImageTransform(const std::string& s)
1928 int total_transform = 0;
1930 std::string transform_names[8];
1931 transform_names[0] = "i";
1932 transform_names[1] = "r90";
1933 transform_names[2] = "r180";
1934 transform_names[3] = "r270";
1935 transform_names[4] = "fx";
1936 transform_names[6] = "fy";
1938 std::size_t pos = 0;
1939 while(pos < s.size())
1942 for (int i = 0; i <= 7; ++i)
1944 const std::string &name_i = transform_names[i];
1946 if (s[pos] == ('0' + i))
1952 else if (!(name_i.empty()) &&
1953 lowercase(s.substr(pos, name_i.size())) == name_i)
1956 pos += name_i.size();
1963 // Multiply total_transform and transform in the group D4
1966 new_total = (transform + total_transform) % 4;
1968 new_total = (transform - total_transform + 8) % 4;
1969 if ((transform >= 4) ^ (total_transform >= 4))
1972 total_transform = new_total;
1974 return total_transform;
1977 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1979 if (transform % 2 == 0)
1982 return core::dimension2d<u32>(dim.Height, dim.Width);
1985 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1987 if (src == NULL || dst == NULL)
1990 core::dimension2d<u32> dstdim = dst->getDimension();
1993 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1994 assert(transform <= 7);
1997 Compute the transformation from source coordinates (sx,sy)
1998 to destination coordinates (dx,dy).
2002 if (transform == 0) // identity
2003 sxn = 0, syn = 2; // sx = dx, sy = dy
2004 else if (transform == 1) // rotate by 90 degrees ccw
2005 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2006 else if (transform == 2) // rotate by 180 degrees
2007 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2008 else if (transform == 3) // rotate by 270 degrees ccw
2009 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2010 else if (transform == 4) // flip x
2011 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2012 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2013 sxn = 2, syn = 0; // sx = dy, sy = dx
2014 else if (transform == 6) // flip y
2015 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2016 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2017 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2019 for (u32 dy=0; dy<dstdim.Height; dy++)
2020 for (u32 dx=0; dx<dstdim.Width; dx++)
2022 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2023 u32 sx = entries[sxn];
2024 u32 sy = entries[syn];
2025 video::SColor c = src->getPixel(sx,sy);
2026 dst->setPixel(dx,dy,c);
2030 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2033 if (isKnownSourceImage("override_normal.png"))
2034 return getTexture("override_normal.png", &id);
2035 std::string fname_base = name;
2036 std::string normal_ext = "_normal.png";
2037 size_t pos = fname_base.find(".");
2038 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2039 if (isKnownSourceImage(fname_normal)) {
2040 // look for image extension and replace it
2042 while ((i = fname_base.find(".", i)) != std::string::npos) {
2043 fname_base.replace(i, 4, normal_ext);
2044 i += normal_ext.length();
2046 return getTexture(fname_base, &id);