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");
196 const core::dimension2d<u32> dim = orig->getDimension();
198 // Don't upscale 1px images. They don't benefit from it anyway
199 // (wouldn't have been blurred) and MIGHT be sun/moon tonemaps.
200 if ((dim.Width <= 1) || (dim.Height <= 1))
203 /* Calculate scaling needed to make the shortest texture dimension
204 * equal to the target minimum. If e.g. this is a vertical frames
205 * animation, the short dimension will be the real size.
207 u32 xscale = scaleto / dim.Width;
208 u32 yscale = scaleto / dim.Height;
209 u32 scale = (xscale > yscale) ? xscale : yscale;
211 // Never downscale; only scale up by 2x or more.
213 u32 w = scale * dim.Width;
214 u32 h = scale * dim.Height;
215 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
216 video::IImage *newimg = driver->createImage(
217 orig->getColorFormat(), newdim);
218 orig->copyToScaling(newimg);
227 SourceImageCache: A cache used for storing source images.
230 class SourceImageCache
233 ~SourceImageCache() {
234 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
235 iter != m_images.end(); iter++) {
236 iter->second->drop();
240 void insert(const std::string &name, video::IImage *img,
241 bool prefer_local, video::IVideoDriver *driver)
243 assert(img); // Pre-condition
245 std::map<std::string, video::IImage*>::iterator n;
246 n = m_images.find(name);
247 if (n != m_images.end()){
252 video::IImage* toadd = img;
253 bool need_to_grab = true;
255 // Try to use local texture instead if asked to
257 std::string path = getTexturePath(name);
259 video::IImage *img2 = driver->createImageFromFile(path.c_str());
262 need_to_grab = false;
267 /* Apply the "clean transparent" filter to textures, removing borders on transparent textures.
268 * PNG optimizers discard RGB values of fully-transparent pixels, but filters may expose the
269 * replacement colors at borders by blending to them; this filter compensates for that by
270 * filling in those RGB values from nearby pixels.
272 if (g_settings->getBool("texture_clean_transparent")) {
273 const core::dimension2d<u32> dim = toadd->getDimension();
275 // Walk each pixel looking for ones that will show as transparent.
276 for (u32 ctrx = 0; ctrx < dim.Width; ctrx++)
277 for (u32 ctry = 0; ctry < dim.Height; ctry++) {
278 irr::video::SColor c = toadd->getPixel(ctrx, ctry);
279 if (c.getAlpha() > 127)
282 // Sample size and total weighted r, g, b values.
283 u32 ss = 0, sr = 0, sg = 0, sb = 0;
285 // Walk each neighbor pixel (clipped to image bounds).
286 for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
287 sx <= (ctrx + 1) && sx < dim.Width; sx++)
288 for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
289 sy <= (ctry + 1) && sy < dim.Height; sy++) {
291 // Ignore the center pixel (its RGB is already
292 // presumed meaningless).
293 if ((sx == ctrx) && (sy == ctry))
296 // Ignore other nearby pixels that would be
297 // transparent upon display.
298 irr::video::SColor d = toadd->getPixel(sx, sy);
299 if(d.getAlpha() < 128)
302 // Add one weighted sample.
309 // If we found any neighbor RGB data, set pixel to average
310 // weighted by alpha.
315 toadd->setPixel(ctrx, ctry, c);
322 m_images[name] = toadd;
324 video::IImage* get(const std::string &name)
326 std::map<std::string, video::IImage*>::iterator n;
327 n = m_images.find(name);
328 if (n != m_images.end())
332 // Primarily fetches from cache, secondarily tries to read from filesystem
333 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
335 std::map<std::string, video::IImage*>::iterator n;
336 n = m_images.find(name);
337 if (n != m_images.end()){
338 n->second->grab(); // Grab for caller
341 video::IVideoDriver* driver = device->getVideoDriver();
342 std::string path = getTexturePath(name);
344 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
345 <<name<<"\""<<std::endl;
348 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
350 video::IImage *img = driver->createImageFromFile(path.c_str());
353 m_images[name] = img;
354 img->grab(); // Grab for caller
359 std::map<std::string, video::IImage*> m_images;
366 class TextureSource : public IWritableTextureSource
369 TextureSource(IrrlichtDevice *device);
370 virtual ~TextureSource();
374 Now, assume a texture with the id 1 exists, and has the name
375 "stone.png^mineral1".
376 Then a random thread calls getTextureId for a texture called
377 "stone.png^mineral1^crack0".
378 ...Now, WTF should happen? Well:
379 - getTextureId strips off stuff recursively from the end until
380 the remaining part is found, or nothing is left when
381 something is stripped out
383 But it is slow to search for textures by names and modify them
385 - ContentFeatures is made to contain ids for the basic plain
387 - Crack textures can be slow by themselves, but the framework
391 - Assume a texture with the id 1 exists, and has the name
392 "stone.png^mineral_coal.png".
393 - Now getNodeTile() stumbles upon a node which uses
394 texture id 1, and determines that MATERIAL_FLAG_CRACK
395 must be applied to the tile
396 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
397 has received the current crack level 0 from the client. It
398 finds out the name of the texture with getTextureName(1),
399 appends "^crack0" to it and gets a new texture id with
400 getTextureId("stone.png^mineral_coal.png^crack0").
405 Gets a texture id from cache or
406 - if main thread, generates the texture, adds to cache and returns id.
407 - if other thread, adds to request queue and waits for main thread.
409 The id 0 points to a NULL texture. It is returned in case of error.
411 u32 getTextureId(const std::string &name);
413 // Finds out the name of a cached texture.
414 std::string getTextureName(u32 id);
417 If texture specified by the name pointed by the id doesn't
418 exist, create it, then return the cached texture.
420 Can be called from any thread. If called from some other thread
421 and not found in cache, the call is queued to the main thread
424 video::ITexture* getTexture(u32 id);
426 video::ITexture* getTexture(const std::string &name, u32 *id);
428 // Returns a pointer to the irrlicht device
429 virtual IrrlichtDevice* getDevice()
434 bool isKnownSourceImage(const std::string &name)
436 bool is_known = false;
437 bool cache_found = m_source_image_existence.get(name, &is_known);
440 // Not found in cache; find out if a local file exists
441 is_known = (getTexturePath(name) != "");
442 m_source_image_existence.set(name, is_known);
446 // Processes queued texture requests from other threads.
447 // Shall be called from the main thread.
450 // Insert an image into the cache without touching the filesystem.
451 // Shall be called from the main thread.
452 void insertSourceImage(const std::string &name, video::IImage *img);
454 // Rebuild images and textures from the current set of source images
455 // Shall be called from the main thread.
456 void rebuildImagesAndTextures();
458 // Render a mesh to a texture.
459 // Returns NULL if render-to-texture failed.
460 // Shall be called from the main thread.
461 video::ITexture* generateTextureFromMesh(
462 const TextureFromMeshParams ¶ms);
464 // Generates an image from a full string like
465 // "stone.png^mineral_coal.png^[crack:1:0".
466 // Shall be called from the main thread.
467 video::IImage* generateImage(const std::string &name);
469 video::ITexture* getNormalTexture(const std::string &name);
472 // The id of the thread that is allowed to use irrlicht directly
473 threadid_t m_main_thread;
474 // The irrlicht device
475 IrrlichtDevice *m_device;
477 // Cache of source images
478 // This should be only accessed from the main thread
479 SourceImageCache m_sourcecache;
481 // Generate a texture
482 u32 generateTexture(const std::string &name);
484 // Generate image based on a string like "stone.png" or "[crack:1:0".
485 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
486 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
488 // Thread-safe cache of what source images are known (true = known)
489 MutexedMap<std::string, bool> m_source_image_existence;
491 // A texture id is index in this array.
492 // The first position contains a NULL texture.
493 std::vector<TextureInfo> m_textureinfo_cache;
494 // Maps a texture name to an index in the former.
495 std::map<std::string, u32> m_name_to_id;
496 // The two former containers are behind this mutex
497 JMutex m_textureinfo_cache_mutex;
499 // Queued texture fetches (to be processed by the main thread)
500 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
502 // Textures that have been overwritten with other ones
503 // but can't be deleted because the ITexture* might still be used
504 std::vector<video::ITexture*> m_texture_trash;
506 // Cached settings needed for making textures from meshes
507 bool m_setting_trilinear_filter;
508 bool m_setting_bilinear_filter;
509 bool m_setting_anisotropic_filter;
512 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
514 return new TextureSource(device);
517 TextureSource::TextureSource(IrrlichtDevice *device):
520 assert(m_device); // Pre-condition
522 m_main_thread = get_current_thread_id();
524 // Add a NULL TextureInfo as the first index, named ""
525 m_textureinfo_cache.push_back(TextureInfo(""));
526 m_name_to_id[""] = 0;
528 // Cache some settings
529 // Note: Since this is only done once, the game must be restarted
530 // for these settings to take effect
531 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
532 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
533 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
536 TextureSource::~TextureSource()
538 video::IVideoDriver* driver = m_device->getVideoDriver();
540 unsigned int textures_before = driver->getTextureCount();
542 for (std::vector<TextureInfo>::iterator iter =
543 m_textureinfo_cache.begin();
544 iter != m_textureinfo_cache.end(); iter++)
548 driver->removeTexture(iter->texture);
550 m_textureinfo_cache.clear();
552 for (std::vector<video::ITexture*>::iterator iter =
553 m_texture_trash.begin(); iter != m_texture_trash.end();
555 video::ITexture *t = *iter;
557 //cleanup trashed texture
558 driver->removeTexture(t);
561 infostream << "~TextureSource() "<< textures_before << "/"
562 << driver->getTextureCount() << std::endl;
565 u32 TextureSource::getTextureId(const std::string &name)
567 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
571 See if texture already exists
573 JMutexAutoLock lock(m_textureinfo_cache_mutex);
574 std::map<std::string, u32>::iterator n;
575 n = m_name_to_id.find(name);
576 if (n != m_name_to_id.end())
585 if (get_current_thread_id() == m_main_thread)
587 return generateTexture(name);
591 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
593 // We're gonna ask the result to be put into here
594 static ResultQueue<std::string, u32, u8, u8> result_queue;
596 // Throw a request in
597 m_get_texture_queue.add(name, 0, 0, &result_queue);
599 /*infostream<<"Waiting for texture from main thread, name=\""
600 <<name<<"\""<<std::endl;*/
605 // Wait result for a second
606 GetResult<std::string, u32, u8, u8>
607 result = result_queue.pop_front(1000);
609 if (result.key == name) {
614 catch(ItemNotFoundException &e)
616 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
621 infostream<<"getTextureId(): Failed"<<std::endl;
626 // Draw an image on top of an another one, using the alpha channel of the
628 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
629 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
631 // Like blit_with_alpha, but only modifies destination pixels that
633 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
634 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
636 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
637 // and modifies any destination pixels that are not fully transparent
638 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
639 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
641 // Apply a mask to an image
642 static void apply_mask(video::IImage *mask, video::IImage *dst,
643 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
645 // Draw or overlay a crack
646 static void draw_crack(video::IImage *crack, video::IImage *dst,
647 bool use_overlay, s32 frame_count, s32 progression,
648 video::IVideoDriver *driver);
651 void brighten(video::IImage *image);
652 // Parse a transform name
653 u32 parseImageTransform(const std::string& s);
654 // Apply transform to image dimension
655 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
656 // Apply transform to image data
657 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
660 This method generates all the textures
662 u32 TextureSource::generateTexture(const std::string &name)
664 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
666 // Empty name means texture 0
668 infostream<<"generateTexture(): name is empty"<<std::endl;
674 See if texture already exists
676 JMutexAutoLock lock(m_textureinfo_cache_mutex);
677 std::map<std::string, u32>::iterator n;
678 n = m_name_to_id.find(name);
679 if (n != m_name_to_id.end()) {
685 Calling only allowed from main thread
687 if (get_current_thread_id() != m_main_thread) {
688 errorstream<<"TextureSource::generateTexture() "
689 "called not from main thread"<<std::endl;
693 video::IVideoDriver *driver = m_device->getVideoDriver();
694 sanity_check(driver);
696 video::IImage *origimg = generateImage(name);
697 video::IImage *img = textureMinSizeUpscale(driver, origimg);
699 video::ITexture *tex = NULL;
703 img = Align2Npot2(img, driver);
705 // Create texture from resulting image
706 tex = driver->addTexture(name.c_str(), img);
708 if((origimg != NULL) && (img != origimg))
713 Add texture to caches (add NULL textures too)
716 JMutexAutoLock lock(m_textureinfo_cache_mutex);
718 u32 id = m_textureinfo_cache.size();
719 TextureInfo ti(name, tex);
720 m_textureinfo_cache.push_back(ti);
721 m_name_to_id[name] = id;
726 std::string TextureSource::getTextureName(u32 id)
728 JMutexAutoLock lock(m_textureinfo_cache_mutex);
730 if (id >= m_textureinfo_cache.size())
732 errorstream<<"TextureSource::getTextureName(): id="<<id
733 <<" >= m_textureinfo_cache.size()="
734 <<m_textureinfo_cache.size()<<std::endl;
738 return m_textureinfo_cache[id].name;
741 video::ITexture* TextureSource::getTexture(u32 id)
743 JMutexAutoLock lock(m_textureinfo_cache_mutex);
745 if (id >= m_textureinfo_cache.size())
748 return m_textureinfo_cache[id].texture;
751 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
753 u32 actual_id = getTextureId(name);
757 return getTexture(actual_id);
760 void TextureSource::processQueue()
765 //NOTE this is only thread safe for ONE consumer thread!
766 if (!m_get_texture_queue.empty())
768 GetRequest<std::string, u32, u8, u8>
769 request = m_get_texture_queue.pop();
771 /*infostream<<"TextureSource::processQueue(): "
772 <<"got texture request with "
773 <<"name=\""<<request.key<<"\""
776 m_get_texture_queue.pushResult(request, generateTexture(request.key));
780 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
782 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
784 sanity_check(get_current_thread_id() == m_main_thread);
786 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
787 m_source_image_existence.set(name, true);
790 void TextureSource::rebuildImagesAndTextures()
792 JMutexAutoLock lock(m_textureinfo_cache_mutex);
794 video::IVideoDriver* driver = m_device->getVideoDriver();
795 sanity_check(driver);
798 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
799 TextureInfo *ti = &m_textureinfo_cache[i];
800 video::IImage *origimg = generateImage(ti->name);
801 video::IImage *img = textureMinSizeUpscale(driver, origimg);
803 img = Align2Npot2(img, driver);
804 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
805 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
807 // Create texture from resulting image
808 video::ITexture *t = NULL;
810 t = driver->addTexture(ti->name.c_str(), img);
812 if(origimg && (origimg != img))
815 video::ITexture *t_old = ti->texture;
820 m_texture_trash.push_back(t_old);
824 video::ITexture* TextureSource::generateTextureFromMesh(
825 const TextureFromMeshParams ¶ms)
827 video::IVideoDriver *driver = m_device->getVideoDriver();
828 sanity_check(driver);
831 const GLubyte* renderstr = glGetString(GL_RENDERER);
832 std::string renderer((char*) renderstr);
834 // use no render to texture hack
836 (renderer.find("Adreno") != std::string::npos) ||
837 (renderer.find("Mali") != std::string::npos) ||
838 (renderer.find("Immersion") != std::string::npos) ||
839 (renderer.find("Tegra") != std::string::npos) ||
840 g_settings->getBool("inventory_image_hack")
842 // Get a scene manager
843 scene::ISceneManager *smgr_main = m_device->getSceneManager();
844 sanity_check(smgr_main);
845 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
848 const float scaling = 0.2;
850 scene::IMeshSceneNode* meshnode =
851 smgr->addMeshSceneNode(params.mesh, NULL,
852 -1, v3f(0,0,0), v3f(0,0,0),
853 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
854 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
855 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
856 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
857 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
858 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
860 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
861 params.camera_position, params.camera_lookat);
862 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
863 camera->setProjectionMatrix(params.camera_projection_matrix, false);
865 smgr->setAmbientLight(params.ambient_light);
866 smgr->addLightSceneNode(0,
867 params.light_position,
869 params.light_radius*scaling);
871 core::dimension2d<u32> screen = driver->getScreenSize();
874 driver->beginScene(true, true, video::SColor(0,0,0,0));
875 driver->clearZBuffer();
878 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
880 irr::video::IImage* rawImage =
881 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
883 u8* pixels = static_cast<u8*>(rawImage->lock());
890 core::rect<s32> source(
891 screen.Width /2 - (screen.Width * (scaling / 2)),
892 screen.Height/2 - (screen.Height * (scaling / 2)),
893 screen.Width /2 + (screen.Width * (scaling / 2)),
894 screen.Height/2 + (screen.Height * (scaling / 2))
897 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
898 partsize.Width, partsize.Height, GL_RGBA,
899 GL_UNSIGNED_BYTE, pixels);
903 // Drop scene manager
906 unsigned int pixelcount = partsize.Width*partsize.Height;
909 for (unsigned int i=0; i < pixelcount; i++) {
927 video::IImage* inventory_image =
928 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
930 rawImage->copyToScaling(inventory_image);
933 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
934 inventory_image->drop();
937 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
941 driver->makeColorKeyTexture(rtt, v2s32(0,0));
943 if (params.delete_texture_on_shutdown)
944 m_texture_trash.push_back(rtt);
950 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
952 static bool warned = false;
955 errorstream<<"TextureSource::generateTextureFromMesh(): "
956 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
962 // Create render target texture
963 video::ITexture *rtt = driver->addRenderTargetTexture(
964 params.dim, params.rtt_texture_name.c_str(),
965 video::ECF_A8R8G8B8);
968 errorstream<<"TextureSource::generateTextureFromMesh(): "
969 <<"addRenderTargetTexture returned NULL."<<std::endl;
974 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
975 driver->removeTexture(rtt);
976 errorstream<<"TextureSource::generateTextureFromMesh(): "
977 <<"failed to set render target"<<std::endl;
981 // Get a scene manager
982 scene::ISceneManager *smgr_main = m_device->getSceneManager();
984 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
987 scene::IMeshSceneNode* meshnode =
988 smgr->addMeshSceneNode(params.mesh, NULL,
989 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
990 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
991 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
992 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
993 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
994 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
996 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
997 params.camera_position, params.camera_lookat);
998 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
999 camera->setProjectionMatrix(params.camera_projection_matrix, false);
1001 smgr->setAmbientLight(params.ambient_light);
1002 smgr->addLightSceneNode(0,
1003 params.light_position,
1005 params.light_radius);
1008 driver->beginScene(true, true, video::SColor(0,0,0,0));
1012 // Drop scene manager
1015 // Unset render target
1016 driver->setRenderTarget(0, false, true, 0);
1018 if (params.delete_texture_on_shutdown)
1019 m_texture_trash.push_back(rtt);
1024 video::IImage* TextureSource::generateImage(const std::string &name)
1030 const char separator = '^';
1031 const char paren_open = '(';
1032 const char paren_close = ')';
1034 // Find last separator in the name
1035 s32 last_separator_pos = -1;
1037 for (s32 i = name.size() - 1; i >= 0; i--) {
1040 if (paren_bal == 0) {
1041 last_separator_pos = i;
1042 i = -1; // break out of loop
1046 if (paren_bal == 0) {
1047 errorstream << "generateImage(): unbalanced parentheses"
1048 << "(extranous '(') while generating texture \""
1049 << name << "\"" << std::endl;
1061 if (paren_bal > 0) {
1062 errorstream << "generateImage(): unbalanced parentheses"
1063 << "(missing matching '(') while generating texture \""
1064 << name << "\"" << std::endl;
1069 video::IImage *baseimg = NULL;
1072 If separator was found, make the base image
1073 using a recursive call.
1075 if (last_separator_pos != -1) {
1076 baseimg = generateImage(name.substr(0, last_separator_pos));
1080 video::IVideoDriver* driver = m_device->getVideoDriver();
1081 sanity_check(driver);
1084 Parse out the last part of the name of the image and act
1088 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1091 If this name is enclosed in parentheses, generate it
1092 and blit it onto the base image
1094 if (last_part_of_name[0] == paren_open
1095 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1096 std::string name2 = last_part_of_name.substr(1,
1097 last_part_of_name.size() - 2);
1098 video::IImage *tmp = generateImage(name2);
1100 errorstream << "generateImage(): "
1101 "Failed to generate \"" << name2 << "\""
1105 core::dimension2d<u32> dim = tmp->getDimension();
1107 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1108 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1110 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1111 // Generate image according to part of name
1112 errorstream << "generateImage(): "
1113 "Failed to generate \"" << last_part_of_name << "\""
1117 // If no resulting image, print a warning
1118 if (baseimg == NULL) {
1119 errorstream << "generateImage(): baseimg is NULL (attempted to"
1120 " create texture \"" << name << "\")" << std::endl;
1127 #include <GLES/gl.h>
1129 * Check and align image to npot2 if required by hardware
1130 * @param image image to check for npot2 alignment
1131 * @param driver driver to use for image operations
1132 * @return image or copy of image aligned to npot2
1134 video::IImage * Align2Npot2(video::IImage * image,
1135 video::IVideoDriver* driver)
1137 if (image == NULL) {
1141 core::dimension2d<u32> dim = image->getDimension();
1143 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1144 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1148 unsigned int height = npot2(dim.Height);
1149 unsigned int width = npot2(dim.Width);
1151 if ((dim.Height == height) &&
1152 (dim.Width == width)) {
1156 if (dim.Height > height) {
1160 if (dim.Width > width) {
1164 video::IImage *targetimage =
1165 driver->createImage(video::ECF_A8R8G8B8,
1166 core::dimension2d<u32>(width, height));
1168 if (targetimage != NULL) {
1169 image->copyToScaling(targetimage);
1177 bool TextureSource::generateImagePart(std::string part_of_name,
1178 video::IImage *& baseimg)
1180 video::IVideoDriver* driver = m_device->getVideoDriver();
1181 sanity_check(driver);
1183 // Stuff starting with [ are special commands
1184 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1186 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1188 image = Align2Npot2(image, driver);
1190 if (image == NULL) {
1191 if (part_of_name != "") {
1192 if (part_of_name.find("_normal.png") == std::string::npos){
1193 errorstream<<"generateImage(): Could not load image \""
1194 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1195 errorstream<<"generateImage(): Creating a dummy"
1196 <<" image for \""<<part_of_name<<"\""<<std::endl;
1198 infostream<<"generateImage(): Could not load normal map \""
1199 <<part_of_name<<"\""<<std::endl;
1200 infostream<<"generateImage(): Creating a dummy"
1201 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1205 // Just create a dummy image
1206 //core::dimension2d<u32> dim(2,2);
1207 core::dimension2d<u32> dim(1,1);
1208 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1209 sanity_check(image != NULL);
1210 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1211 image->setPixel(1,0, video::SColor(255,0,255,0));
1212 image->setPixel(0,1, video::SColor(255,0,0,255));
1213 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1214 image->setPixel(0,0, video::SColor(255,myrand()%256,
1215 myrand()%256,myrand()%256));
1216 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1217 myrand()%256,myrand()%256));
1218 image->setPixel(0,1, video::SColor(255,myrand()%256,
1219 myrand()%256,myrand()%256));
1220 image->setPixel(1,1, video::SColor(255,myrand()%256,
1221 myrand()%256,myrand()%256));*/
1224 // If base image is NULL, load as base.
1225 if (baseimg == NULL)
1227 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1229 Copy it this way to get an alpha channel.
1230 Otherwise images with alpha cannot be blitted on
1231 images that don't have alpha in the original file.
1233 core::dimension2d<u32> dim = image->getDimension();
1234 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1235 image->copyTo(baseimg);
1237 // Else blit on base.
1240 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1241 // Size of the copied area
1242 core::dimension2d<u32> dim = image->getDimension();
1243 //core::dimension2d<u32> dim(16,16);
1244 // Position to copy the blitted to in the base image
1245 core::position2d<s32> pos_to(0,0);
1246 // Position to copy the blitted from in the blitted image
1247 core::position2d<s32> pos_from(0,0);
1249 /*image->copyToWithAlpha(baseimg, pos_to,
1250 core::rect<s32>(pos_from, dim),
1251 video::SColor(255,255,255,255),
1253 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1260 // A special texture modification
1262 /*infostream<<"generateImage(): generating special "
1263 <<"modification \""<<part_of_name<<"\""
1269 Adds a cracking texture
1270 N = animation frame count, P = crack progression
1272 if (part_of_name.substr(0,6) == "[crack")
1274 if (baseimg == NULL) {
1275 errorstream<<"generateImagePart(): baseimg == NULL "
1276 <<"for part_of_name=\""<<part_of_name
1277 <<"\", cancelling."<<std::endl;
1281 // Crack image number and overlay option
1282 bool use_overlay = (part_of_name[6] == 'o');
1283 Strfnd sf(part_of_name);
1285 s32 frame_count = stoi(sf.next(":"));
1286 s32 progression = stoi(sf.next(":"));
1291 It is an image with a number of cracking stages
1294 video::IImage *img_crack = m_sourcecache.getOrLoad(
1295 "crack_anylength.png", m_device);
1297 if (img_crack && progression >= 0)
1299 draw_crack(img_crack, baseimg,
1300 use_overlay, frame_count,
1301 progression, driver);
1306 [combine:WxH:X,Y=filename:X,Y=filename2
1307 Creates a bigger texture from an amount of smaller ones
1309 else if (part_of_name.substr(0,8) == "[combine")
1311 Strfnd sf(part_of_name);
1313 u32 w0 = stoi(sf.next("x"));
1314 u32 h0 = stoi(sf.next(":"));
1315 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1316 core::dimension2d<u32> dim(w0,h0);
1317 if (baseimg == NULL) {
1318 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1319 baseimg->fill(video::SColor(0,0,0,0));
1321 while (sf.atend() == false) {
1322 u32 x = stoi(sf.next(","));
1323 u32 y = stoi(sf.next("="));
1324 std::string filename = sf.next(":");
1325 infostream<<"Adding \""<<filename
1326 <<"\" to combined ("<<x<<","<<y<<")"
1328 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1330 core::dimension2d<u32> dim = img->getDimension();
1331 infostream<<"Size "<<dim.Width
1332 <<"x"<<dim.Height<<std::endl;
1333 core::position2d<s32> pos_base(x, y);
1334 video::IImage *img2 =
1335 driver->createImage(video::ECF_A8R8G8B8, dim);
1338 /*img2->copyToWithAlpha(baseimg, pos_base,
1339 core::rect<s32>(v2s32(0,0), dim),
1340 video::SColor(255,255,255,255),
1342 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1345 errorstream << "generateImagePart(): Failed to load image \""
1346 << filename << "\" for [combine" << std::endl;
1353 else if (part_of_name.substr(0,9) == "[brighten")
1355 if (baseimg == NULL) {
1356 errorstream<<"generateImagePart(): baseimg==NULL "
1357 <<"for part_of_name=\""<<part_of_name
1358 <<"\", cancelling."<<std::endl;
1366 Make image completely opaque.
1367 Used for the leaves texture when in old leaves mode, so
1368 that the transparent parts don't look completely black
1369 when simple alpha channel is used for rendering.
1371 else if (part_of_name.substr(0,8) == "[noalpha")
1373 if (baseimg == NULL){
1374 errorstream<<"generateImagePart(): baseimg==NULL "
1375 <<"for part_of_name=\""<<part_of_name
1376 <<"\", cancelling."<<std::endl;
1380 core::dimension2d<u32> dim = baseimg->getDimension();
1382 // Set alpha to full
1383 for (u32 y=0; y<dim.Height; y++)
1384 for (u32 x=0; x<dim.Width; x++)
1386 video::SColor c = baseimg->getPixel(x,y);
1388 baseimg->setPixel(x,y,c);
1393 Convert one color to transparent.
1395 else if (part_of_name.substr(0,11) == "[makealpha:")
1397 if (baseimg == NULL) {
1398 errorstream<<"generateImagePart(): baseimg == NULL "
1399 <<"for part_of_name=\""<<part_of_name
1400 <<"\", cancelling."<<std::endl;
1404 Strfnd sf(part_of_name.substr(11));
1405 u32 r1 = stoi(sf.next(","));
1406 u32 g1 = stoi(sf.next(","));
1407 u32 b1 = stoi(sf.next(""));
1408 std::string filename = sf.next("");
1410 core::dimension2d<u32> dim = baseimg->getDimension();
1412 /*video::IImage *oldbaseimg = baseimg;
1413 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1414 oldbaseimg->copyTo(baseimg);
1415 oldbaseimg->drop();*/
1417 // Set alpha to full
1418 for (u32 y=0; y<dim.Height; y++)
1419 for (u32 x=0; x<dim.Width; x++)
1421 video::SColor c = baseimg->getPixel(x,y);
1423 u32 g = c.getGreen();
1424 u32 b = c.getBlue();
1425 if (!(r == r1 && g == g1 && b == b1))
1428 baseimg->setPixel(x,y,c);
1433 Rotates and/or flips the image.
1435 N can be a number (between 0 and 7) or a transform name.
1436 Rotations are counter-clockwise.
1438 1 R90 rotate by 90 degrees
1439 2 R180 rotate by 180 degrees
1440 3 R270 rotate by 270 degrees
1442 5 FXR90 flip X then rotate by 90 degrees
1444 7 FYR90 flip Y then rotate by 90 degrees
1446 Note: Transform names can be concatenated to produce
1447 their product (applies the first then the second).
1448 The resulting transform will be equivalent to one of the
1449 eight existing ones, though (see: dihedral group).
1451 else if (part_of_name.substr(0,10) == "[transform")
1453 if (baseimg == NULL) {
1454 errorstream<<"generateImagePart(): baseimg == NULL "
1455 <<"for part_of_name=\""<<part_of_name
1456 <<"\", cancelling."<<std::endl;
1460 u32 transform = parseImageTransform(part_of_name.substr(10));
1461 core::dimension2d<u32> dim = imageTransformDimension(
1462 transform, baseimg->getDimension());
1463 video::IImage *image = driver->createImage(
1464 baseimg->getColorFormat(), dim);
1465 sanity_check(image != NULL);
1466 imageTransform(transform, baseimg, image);
1471 [inventorycube{topimage{leftimage{rightimage
1472 In every subimage, replace ^ with &.
1473 Create an "inventory cube".
1474 NOTE: This should be used only on its own.
1475 Example (a grass block (not actually used in game):
1476 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1478 else if (part_of_name.substr(0,14) == "[inventorycube")
1480 if (baseimg != NULL){
1481 errorstream<<"generateImagePart(): baseimg != NULL "
1482 <<"for part_of_name=\""<<part_of_name
1483 <<"\", cancelling."<<std::endl;
1487 str_replace(part_of_name, '&', '^');
1488 Strfnd sf(part_of_name);
1490 std::string imagename_top = sf.next("{");
1491 std::string imagename_left = sf.next("{");
1492 std::string imagename_right = sf.next("{");
1494 // Generate images for the faces of the cube
1495 video::IImage *img_top = generateImage(imagename_top);
1496 video::IImage *img_left = generateImage(imagename_left);
1497 video::IImage *img_right = generateImage(imagename_right);
1499 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1500 errorstream << "generateImagePart(): Failed to create textures"
1501 << " for inventorycube \"" << part_of_name << "\""
1503 baseimg = generateImage(imagename_top);
1508 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1509 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1511 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1512 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1514 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1515 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1518 // Create textures from images
1519 video::ITexture *texture_top = driver->addTexture(
1520 (imagename_top + "__temp__").c_str(), img_top);
1521 video::ITexture *texture_left = driver->addTexture(
1522 (imagename_left + "__temp__").c_str(), img_left);
1523 video::ITexture *texture_right = driver->addTexture(
1524 (imagename_right + "__temp__").c_str(), img_right);
1525 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1533 Draw a cube mesh into a render target texture
1535 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1536 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1537 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1538 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1539 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1540 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1541 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1542 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1544 TextureFromMeshParams params;
1546 params.dim.set(64, 64);
1547 params.rtt_texture_name = part_of_name + "_RTT";
1548 // We will delete the rtt texture ourselves
1549 params.delete_texture_on_shutdown = false;
1550 params.camera_position.set(0, 1.0, -1.5);
1551 params.camera_position.rotateXZBy(45);
1552 params.camera_lookat.set(0, 0, 0);
1553 // Set orthogonal projection
1554 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1555 1.65, 1.65, 0, 100);
1557 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1558 params.light_position.set(10, 100, -50);
1559 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1560 params.light_radius = 1000;
1562 video::ITexture *rtt = generateTextureFromMesh(params);
1568 driver->removeTexture(texture_top);
1569 driver->removeTexture(texture_left);
1570 driver->removeTexture(texture_right);
1573 baseimg = generateImage(imagename_top);
1577 // Create image of render target
1578 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1579 FATAL_ERROR_IF(!image, "Could not create image of render target");
1582 driver->removeTexture(rtt);
1584 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1587 image->copyTo(baseimg);
1592 [lowpart:percent:filename
1593 Adds the lower part of a texture
1595 else if (part_of_name.substr(0,9) == "[lowpart:")
1597 Strfnd sf(part_of_name);
1599 u32 percent = stoi(sf.next(":"));
1600 std::string filename = sf.next(":");
1601 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1603 if (baseimg == NULL)
1604 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1605 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1608 core::dimension2d<u32> dim = img->getDimension();
1609 core::position2d<s32> pos_base(0, 0);
1610 video::IImage *img2 =
1611 driver->createImage(video::ECF_A8R8G8B8, dim);
1614 core::position2d<s32> clippos(0, 0);
1615 clippos.Y = dim.Height * (100-percent) / 100;
1616 core::dimension2d<u32> clipdim = dim;
1617 clipdim.Height = clipdim.Height * percent / 100 + 1;
1618 core::rect<s32> cliprect(clippos, clipdim);
1619 img2->copyToWithAlpha(baseimg, pos_base,
1620 core::rect<s32>(v2s32(0,0), dim),
1621 video::SColor(255,255,255,255),
1628 Crops a frame of a vertical animation.
1629 N = frame count, I = frame index
1631 else if (part_of_name.substr(0,15) == "[verticalframe:")
1633 Strfnd sf(part_of_name);
1635 u32 frame_count = stoi(sf.next(":"));
1636 u32 frame_index = stoi(sf.next(":"));
1638 if (baseimg == NULL){
1639 errorstream<<"generateImagePart(): baseimg != NULL "
1640 <<"for part_of_name=\""<<part_of_name
1641 <<"\", cancelling."<<std::endl;
1645 v2u32 frame_size = baseimg->getDimension();
1646 frame_size.Y /= frame_count;
1648 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1651 errorstream<<"generateImagePart(): Could not create image "
1652 <<"for part_of_name=\""<<part_of_name
1653 <<"\", cancelling."<<std::endl;
1657 // Fill target image with transparency
1658 img->fill(video::SColor(0,0,0,0));
1660 core::dimension2d<u32> dim = frame_size;
1661 core::position2d<s32> pos_dst(0, 0);
1662 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1663 baseimg->copyToWithAlpha(img, pos_dst,
1664 core::rect<s32>(pos_src, dim),
1665 video::SColor(255,255,255,255),
1673 Applies a mask to an image
1675 else if (part_of_name.substr(0,6) == "[mask:")
1677 if (baseimg == NULL) {
1678 errorstream << "generateImage(): baseimg == NULL "
1679 << "for part_of_name=\"" << part_of_name
1680 << "\", cancelling." << std::endl;
1683 Strfnd sf(part_of_name);
1685 std::string filename = sf.next(":");
1687 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1689 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1690 img->getDimension());
1692 errorstream << "generateImage(): Failed to load \""
1693 << filename << "\".";
1698 Overlays image with given color
1699 color = color as ColorString
1701 else if (part_of_name.substr(0,10) == "[colorize:") {
1702 Strfnd sf(part_of_name);
1704 std::string color_str = sf.next(":");
1705 std::string ratio_str = sf.next(":");
1707 if (baseimg == NULL) {
1708 errorstream << "generateImagePart(): baseimg != NULL "
1709 << "for part_of_name=\"" << part_of_name
1710 << "\", cancelling." << std::endl;
1714 video::SColor color;
1717 if (!parseColorString(color_str, color, false))
1720 if (is_number(ratio_str))
1721 ratio = mystoi(ratio_str, 0, 255);
1723 core::dimension2d<u32> dim = baseimg->getDimension();
1724 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1727 errorstream << "generateImagePart(): Could not create image "
1728 << "for part_of_name=\"" << part_of_name
1729 << "\", cancelling." << std::endl;
1733 img->fill(video::SColor(color));
1734 // Overlay the colored image
1735 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1740 errorstream << "generateImagePart(): Invalid "
1741 " modification: \"" << part_of_name << "\"" << std::endl;
1749 Draw an image on top of an another one, using the alpha channel of the
1752 This exists because IImage::copyToWithAlpha() doesn't seem to always
1755 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1756 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1758 for (u32 y0=0; y0<size.Y; y0++)
1759 for (u32 x0=0; x0<size.X; x0++)
1761 s32 src_x = src_pos.X + x0;
1762 s32 src_y = src_pos.Y + y0;
1763 s32 dst_x = dst_pos.X + x0;
1764 s32 dst_y = dst_pos.Y + y0;
1765 video::SColor src_c = src->getPixel(src_x, src_y);
1766 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1767 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1768 dst->setPixel(dst_x, dst_y, dst_c);
1773 Draw an image on top of an another one, using the alpha channel of the
1774 source image; only modify fully opaque pixels in destinaion
1776 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1777 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1779 for (u32 y0=0; y0<size.Y; y0++)
1780 for (u32 x0=0; x0<size.X; x0++)
1782 s32 src_x = src_pos.X + x0;
1783 s32 src_y = src_pos.Y + y0;
1784 s32 dst_x = dst_pos.X + x0;
1785 s32 dst_y = dst_pos.Y + y0;
1786 video::SColor src_c = src->getPixel(src_x, src_y);
1787 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1788 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1790 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1791 dst->setPixel(dst_x, dst_y, dst_c);
1797 Draw an image on top of an another one, using the specified ratio
1798 modify all partially-opaque pixels in the destination.
1800 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1801 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1803 for (u32 y0 = 0; y0 < size.Y; y0++)
1804 for (u32 x0 = 0; x0 < size.X; x0++)
1806 s32 src_x = src_pos.X + x0;
1807 s32 src_y = src_pos.Y + y0;
1808 s32 dst_x = dst_pos.X + x0;
1809 s32 dst_y = dst_pos.Y + y0;
1810 video::SColor src_c = src->getPixel(src_x, src_y);
1811 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1812 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1815 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1817 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1818 dst->setPixel(dst_x, dst_y, dst_c);
1824 Apply mask to destination
1826 static void apply_mask(video::IImage *mask, video::IImage *dst,
1827 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1829 for (u32 y0 = 0; y0 < size.Y; y0++) {
1830 for (u32 x0 = 0; x0 < size.X; x0++) {
1831 s32 mask_x = x0 + mask_pos.X;
1832 s32 mask_y = y0 + mask_pos.Y;
1833 s32 dst_x = x0 + dst_pos.X;
1834 s32 dst_y = y0 + dst_pos.Y;
1835 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1836 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1837 dst_c.color &= mask_c.color;
1838 dst->setPixel(dst_x, dst_y, dst_c);
1843 static void draw_crack(video::IImage *crack, video::IImage *dst,
1844 bool use_overlay, s32 frame_count, s32 progression,
1845 video::IVideoDriver *driver)
1847 // Dimension of destination image
1848 core::dimension2d<u32> dim_dst = dst->getDimension();
1849 // Dimension of original image
1850 core::dimension2d<u32> dim_crack = crack->getDimension();
1851 // Count of crack stages
1852 s32 crack_count = dim_crack.Height / dim_crack.Width;
1853 // Limit frame_count
1854 if (frame_count > (s32) dim_dst.Height)
1855 frame_count = dim_dst.Height;
1856 if (frame_count < 1)
1858 // Limit progression
1859 if (progression > crack_count-1)
1860 progression = crack_count-1;
1861 // Dimension of a single crack stage
1862 core::dimension2d<u32> dim_crack_cropped(
1866 // Dimension of the scaled crack stage,
1867 // which is the same as the dimension of a single destination frame
1868 core::dimension2d<u32> dim_crack_scaled(
1870 dim_dst.Height / frame_count
1872 // Create cropped and scaled crack images
1873 video::IImage *crack_cropped = driver->createImage(
1874 video::ECF_A8R8G8B8, dim_crack_cropped);
1875 video::IImage *crack_scaled = driver->createImage(
1876 video::ECF_A8R8G8B8, dim_crack_scaled);
1878 if (crack_cropped && crack_scaled)
1881 v2s32 pos_crack(0, progression*dim_crack.Width);
1882 crack->copyTo(crack_cropped,
1884 core::rect<s32>(pos_crack, dim_crack_cropped));
1885 // Scale crack image by copying
1886 crack_cropped->copyToScaling(crack_scaled);
1887 // Copy or overlay crack image onto each frame
1888 for (s32 i = 0; i < frame_count; ++i)
1890 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1893 blit_with_alpha_overlay(crack_scaled, dst,
1894 v2s32(0,0), dst_pos,
1899 blit_with_alpha(crack_scaled, dst,
1900 v2s32(0,0), dst_pos,
1907 crack_scaled->drop();
1910 crack_cropped->drop();
1913 void brighten(video::IImage *image)
1918 core::dimension2d<u32> dim = image->getDimension();
1920 for (u32 y=0; y<dim.Height; y++)
1921 for (u32 x=0; x<dim.Width; x++)
1923 video::SColor c = image->getPixel(x,y);
1924 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1925 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1926 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1927 image->setPixel(x,y,c);
1931 u32 parseImageTransform(const std::string& s)
1933 int total_transform = 0;
1935 std::string transform_names[8];
1936 transform_names[0] = "i";
1937 transform_names[1] = "r90";
1938 transform_names[2] = "r180";
1939 transform_names[3] = "r270";
1940 transform_names[4] = "fx";
1941 transform_names[6] = "fy";
1943 std::size_t pos = 0;
1944 while(pos < s.size())
1947 for (int i = 0; i <= 7; ++i)
1949 const std::string &name_i = transform_names[i];
1951 if (s[pos] == ('0' + i))
1957 else if (!(name_i.empty()) &&
1958 lowercase(s.substr(pos, name_i.size())) == name_i)
1961 pos += name_i.size();
1968 // Multiply total_transform and transform in the group D4
1971 new_total = (transform + total_transform) % 4;
1973 new_total = (transform - total_transform + 8) % 4;
1974 if ((transform >= 4) ^ (total_transform >= 4))
1977 total_transform = new_total;
1979 return total_transform;
1982 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1984 if (transform % 2 == 0)
1987 return core::dimension2d<u32>(dim.Height, dim.Width);
1990 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1992 if (src == NULL || dst == NULL)
1995 core::dimension2d<u32> dstdim = dst->getDimension();
1998 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1999 assert(transform <= 7);
2002 Compute the transformation from source coordinates (sx,sy)
2003 to destination coordinates (dx,dy).
2007 if (transform == 0) // identity
2008 sxn = 0, syn = 2; // sx = dx, sy = dy
2009 else if (transform == 1) // rotate by 90 degrees ccw
2010 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2011 else if (transform == 2) // rotate by 180 degrees
2012 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2013 else if (transform == 3) // rotate by 270 degrees ccw
2014 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2015 else if (transform == 4) // flip x
2016 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2017 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2018 sxn = 2, syn = 0; // sx = dy, sy = dx
2019 else if (transform == 6) // flip y
2020 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2021 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2022 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2024 for (u32 dy=0; dy<dstdim.Height; dy++)
2025 for (u32 dx=0; dx<dstdim.Width; dx++)
2027 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2028 u32 sx = entries[sxn];
2029 u32 sy = entries[syn];
2030 video::SColor c = src->getPixel(sx,sy);
2031 dst->setPixel(dx,dy,c);
2035 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2038 if (isKnownSourceImage("override_normal.png"))
2039 return getTexture("override_normal.png", &id);
2040 std::string fname_base = name;
2041 std::string normal_ext = "_normal.png";
2042 size_t pos = fname_base.find(".");
2043 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2044 if (isKnownSourceImage(fname_normal)) {
2045 // look for image extension and replace it
2047 while ((i = fname_base.find(".", i)) != std::string::npos) {
2048 fname_base.replace(i, 4, normal_ext);
2049 i += normal_ext.length();
2051 return getTexture(fname_base, &id);