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"
35 #include "util/string.h" // for parseColorString()
36 #include "imagefilters.h"
37 #include "guiscalingfilter.h"
46 A cache from texture name to texture path
48 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
51 Replaces the filename extension.
53 std::string image = "a/image.png"
54 replace_ext(image, "jpg")
55 -> image = "a/image.jpg"
56 Returns true on success.
58 static bool replace_ext(std::string &path, const char *ext)
62 // Find place of last dot, fail if \ or / found.
64 for (s32 i=path.size()-1; i>=0; i--)
72 if (path[i] == '\\' || path[i] == '/')
75 // If not found, return an empty string
78 // Else make the new path
79 path = path.substr(0, last_dot_i+1) + ext;
84 Find out the full path of an image by trying different filename
89 std::string getImagePath(std::string path)
91 // A NULL-ended list of possible image extensions
92 const char *extensions[] = {
93 "png", "jpg", "bmp", "tga",
94 "pcx", "ppm", "psd", "wal", "rgb",
97 // If there is no extension, add one
98 if (removeStringEnd(path, extensions) == "")
100 // Check paths until something is found to exist
101 const char **ext = extensions;
103 bool r = replace_ext(path, *ext);
106 if (fs::PathExists(path))
109 while((++ext) != NULL);
115 Gets the path to a texture by first checking if the texture exists
116 in texture_path and if not, using the data path.
118 Checks all supported extensions by replacing the original extension.
120 If not found, returns "".
122 Utilizes a thread-safe cache.
124 std::string getTexturePath(const std::string &filename)
126 std::string fullpath = "";
130 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
135 Check from texture_path
137 std::string texture_path = g_settings->get("texture_path");
138 if (texture_path != "")
140 std::string testpath = texture_path + DIR_DELIM + filename;
141 // Check all filename extensions. Returns "" if not found.
142 fullpath = getImagePath(testpath);
146 Check from default data directory
150 std::string base_path = porting::path_share + DIR_DELIM + "textures"
151 + DIR_DELIM + "base" + DIR_DELIM + "pack";
152 std::string testpath = base_path + DIR_DELIM + filename;
153 // Check all filename extensions. Returns "" if not found.
154 fullpath = getImagePath(testpath);
157 // Add to cache (also an empty result is cached)
158 g_texturename_to_path_cache.set(filename, fullpath);
164 void clearTextureNameCache()
166 g_texturename_to_path_cache.clear();
170 Stores internal information about a texture.
176 video::ITexture *texture;
179 const std::string &name_,
180 video::ITexture *texture_=NULL
189 SourceImageCache: A cache used for storing source images.
192 class SourceImageCache
195 ~SourceImageCache() {
196 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
197 iter != m_images.end(); iter++) {
198 iter->second->drop();
202 void insert(const std::string &name, video::IImage *img,
203 bool prefer_local, video::IVideoDriver *driver)
205 assert(img); // Pre-condition
207 std::map<std::string, video::IImage*>::iterator n;
208 n = m_images.find(name);
209 if (n != m_images.end()){
214 video::IImage* toadd = img;
215 bool need_to_grab = true;
217 // Try to use local texture instead if asked to
219 std::string path = getTexturePath(name);
221 video::IImage *img2 = driver->createImageFromFile(path.c_str());
224 need_to_grab = false;
231 m_images[name] = toadd;
233 video::IImage* get(const std::string &name)
235 std::map<std::string, video::IImage*>::iterator n;
236 n = m_images.find(name);
237 if (n != m_images.end())
241 // Primarily fetches from cache, secondarily tries to read from filesystem
242 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
244 std::map<std::string, video::IImage*>::iterator n;
245 n = m_images.find(name);
246 if (n != m_images.end()){
247 n->second->grab(); // Grab for caller
250 video::IVideoDriver* driver = device->getVideoDriver();
251 std::string path = getTexturePath(name);
253 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
254 <<name<<"\""<<std::endl;
257 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
259 video::IImage *img = driver->createImageFromFile(path.c_str());
262 m_images[name] = img;
263 img->grab(); // Grab for caller
268 std::map<std::string, video::IImage*> m_images;
275 class TextureSource : public IWritableTextureSource
278 TextureSource(IrrlichtDevice *device);
279 virtual ~TextureSource();
283 Now, assume a texture with the id 1 exists, and has the name
284 "stone.png^mineral1".
285 Then a random thread calls getTextureId for a texture called
286 "stone.png^mineral1^crack0".
287 ...Now, WTF should happen? Well:
288 - getTextureId strips off stuff recursively from the end until
289 the remaining part is found, or nothing is left when
290 something is stripped out
292 But it is slow to search for textures by names and modify them
294 - ContentFeatures is made to contain ids for the basic plain
296 - Crack textures can be slow by themselves, but the framework
300 - Assume a texture with the id 1 exists, and has the name
301 "stone.png^mineral_coal.png".
302 - Now getNodeTile() stumbles upon a node which uses
303 texture id 1, and determines that MATERIAL_FLAG_CRACK
304 must be applied to the tile
305 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
306 has received the current crack level 0 from the client. It
307 finds out the name of the texture with getTextureName(1),
308 appends "^crack0" to it and gets a new texture id with
309 getTextureId("stone.png^mineral_coal.png^crack0").
314 Gets a texture id from cache or
315 - if main thread, generates the texture, adds to cache and returns id.
316 - if other thread, adds to request queue and waits for main thread.
318 The id 0 points to a NULL texture. It is returned in case of error.
320 u32 getTextureId(const std::string &name);
322 // Finds out the name of a cached texture.
323 std::string getTextureName(u32 id);
326 If texture specified by the name pointed by the id doesn't
327 exist, create it, then return the cached texture.
329 Can be called from any thread. If called from some other thread
330 and not found in cache, the call is queued to the main thread
333 video::ITexture* getTexture(u32 id);
335 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
338 Get a texture specifically intended for mesh
339 application, i.e. not HUD, compositing, or other 2D
340 use. This texture may be a different size and may
341 have had additional filters applied.
343 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
345 // Returns a pointer to the irrlicht device
346 virtual IrrlichtDevice* getDevice()
351 bool isKnownSourceImage(const std::string &name)
353 bool is_known = false;
354 bool cache_found = m_source_image_existence.get(name, &is_known);
357 // Not found in cache; find out if a local file exists
358 is_known = (getTexturePath(name) != "");
359 m_source_image_existence.set(name, is_known);
363 // Processes queued texture requests from other threads.
364 // Shall be called from the main thread.
367 // Insert an image into the cache without touching the filesystem.
368 // Shall be called from the main thread.
369 void insertSourceImage(const std::string &name, video::IImage *img);
371 // Rebuild images and textures from the current set of source images
372 // Shall be called from the main thread.
373 void rebuildImagesAndTextures();
375 // Render a mesh to a texture.
376 // Returns NULL if render-to-texture failed.
377 // Shall be called from the main thread.
378 video::ITexture* generateTextureFromMesh(
379 const TextureFromMeshParams ¶ms);
381 // Generates an image from a full string like
382 // "stone.png^mineral_coal.png^[crack:1:0".
383 // Shall be called from the main thread.
384 video::IImage* generateImage(const std::string &name);
386 video::ITexture* getNormalTexture(const std::string &name);
387 video::SColor getTextureAverageColor(const std::string &name);
388 video::ITexture *getShaderFlagsTexture(
389 bool normamap_present, bool tileable_vertical, bool tileable_horizontal);
393 // The id of the thread that is allowed to use irrlicht directly
394 threadid_t m_main_thread;
395 // The irrlicht device
396 IrrlichtDevice *m_device;
398 // Cache of source images
399 // This should be only accessed from the main thread
400 SourceImageCache m_sourcecache;
402 // Generate a texture
403 u32 generateTexture(const std::string &name);
405 // Generate image based on a string like "stone.png" or "[crack:1:0".
406 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
407 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
409 // Thread-safe cache of what source images are known (true = known)
410 MutexedMap<std::string, bool> m_source_image_existence;
412 // A texture id is index in this array.
413 // The first position contains a NULL texture.
414 std::vector<TextureInfo> m_textureinfo_cache;
415 // Maps a texture name to an index in the former.
416 std::map<std::string, u32> m_name_to_id;
417 // The two former containers are behind this mutex
418 JMutex m_textureinfo_cache_mutex;
420 // Queued texture fetches (to be processed by the main thread)
421 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
423 // Textures that have been overwritten with other ones
424 // but can't be deleted because the ITexture* might still be used
425 std::vector<video::ITexture*> m_texture_trash;
427 // Cached settings needed for making textures from meshes
428 bool m_setting_trilinear_filter;
429 bool m_setting_bilinear_filter;
430 bool m_setting_anisotropic_filter;
433 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
435 return new TextureSource(device);
438 TextureSource::TextureSource(IrrlichtDevice *device):
441 assert(m_device); // Pre-condition
443 m_main_thread = get_current_thread_id();
445 // Add a NULL TextureInfo as the first index, named ""
446 m_textureinfo_cache.push_back(TextureInfo(""));
447 m_name_to_id[""] = 0;
449 // Cache some settings
450 // Note: Since this is only done once, the game must be restarted
451 // for these settings to take effect
452 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
453 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
454 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
457 TextureSource::~TextureSource()
459 video::IVideoDriver* driver = m_device->getVideoDriver();
461 unsigned int textures_before = driver->getTextureCount();
463 for (std::vector<TextureInfo>::iterator iter =
464 m_textureinfo_cache.begin();
465 iter != m_textureinfo_cache.end(); iter++)
469 driver->removeTexture(iter->texture);
471 m_textureinfo_cache.clear();
473 for (std::vector<video::ITexture*>::iterator iter =
474 m_texture_trash.begin(); iter != m_texture_trash.end();
476 video::ITexture *t = *iter;
478 //cleanup trashed texture
479 driver->removeTexture(t);
482 infostream << "~TextureSource() "<< textures_before << "/"
483 << driver->getTextureCount() << std::endl;
486 u32 TextureSource::getTextureId(const std::string &name)
488 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
492 See if texture already exists
494 JMutexAutoLock lock(m_textureinfo_cache_mutex);
495 std::map<std::string, u32>::iterator n;
496 n = m_name_to_id.find(name);
497 if (n != m_name_to_id.end())
506 if (get_current_thread_id() == m_main_thread)
508 return generateTexture(name);
512 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
514 // We're gonna ask the result to be put into here
515 static ResultQueue<std::string, u32, u8, u8> result_queue;
517 // Throw a request in
518 m_get_texture_queue.add(name, 0, 0, &result_queue);
520 /*infostream<<"Waiting for texture from main thread, name=\""
521 <<name<<"\""<<std::endl;*/
526 // Wait result for a second
527 GetResult<std::string, u32, u8, u8>
528 result = result_queue.pop_front(1000);
530 if (result.key == name) {
535 catch(ItemNotFoundException &e)
537 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
542 infostream<<"getTextureId(): Failed"<<std::endl;
547 // Draw an image on top of an another one, using the alpha channel of the
549 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
550 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552 // Like blit_with_alpha, but only modifies destination pixels that
554 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
555 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
557 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
558 // and modifies any destination pixels that are not fully transparent
559 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
560 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
562 // Apply a mask to an image
563 static void apply_mask(video::IImage *mask, video::IImage *dst,
564 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
566 // Draw or overlay a crack
567 static void draw_crack(video::IImage *crack, video::IImage *dst,
568 bool use_overlay, s32 frame_count, s32 progression,
569 video::IVideoDriver *driver);
572 void brighten(video::IImage *image);
573 // Parse a transform name
574 u32 parseImageTransform(const std::string& s);
575 // Apply transform to image dimension
576 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
577 // Apply transform to image data
578 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
581 This method generates all the textures
583 u32 TextureSource::generateTexture(const std::string &name)
585 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
587 // Empty name means texture 0
589 infostream<<"generateTexture(): name is empty"<<std::endl;
595 See if texture already exists
597 JMutexAutoLock lock(m_textureinfo_cache_mutex);
598 std::map<std::string, u32>::iterator n;
599 n = m_name_to_id.find(name);
600 if (n != m_name_to_id.end()) {
606 Calling only allowed from main thread
608 if (get_current_thread_id() != m_main_thread) {
609 errorstream<<"TextureSource::generateTexture() "
610 "called not from main thread"<<std::endl;
614 video::IVideoDriver *driver = m_device->getVideoDriver();
615 sanity_check(driver);
617 video::IImage *img = generateImage(name);
619 video::ITexture *tex = NULL;
623 img = Align2Npot2(img, driver);
625 // Create texture from resulting image
626 tex = driver->addTexture(name.c_str(), img);
627 guiScalingCache(io::path(name.c_str()), driver, img);
632 Add texture to caches (add NULL textures too)
635 JMutexAutoLock lock(m_textureinfo_cache_mutex);
637 u32 id = m_textureinfo_cache.size();
638 TextureInfo ti(name, tex);
639 m_textureinfo_cache.push_back(ti);
640 m_name_to_id[name] = id;
645 std::string TextureSource::getTextureName(u32 id)
647 JMutexAutoLock lock(m_textureinfo_cache_mutex);
649 if (id >= m_textureinfo_cache.size())
651 errorstream<<"TextureSource::getTextureName(): id="<<id
652 <<" >= m_textureinfo_cache.size()="
653 <<m_textureinfo_cache.size()<<std::endl;
657 return m_textureinfo_cache[id].name;
660 video::ITexture* TextureSource::getTexture(u32 id)
662 JMutexAutoLock lock(m_textureinfo_cache_mutex);
664 if (id >= m_textureinfo_cache.size())
667 return m_textureinfo_cache[id].texture;
670 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
672 u32 actual_id = getTextureId(name);
676 return getTexture(actual_id);
679 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
681 return getTexture(name + "^[applyfiltersformesh", id);
684 void TextureSource::processQueue()
689 //NOTE this is only thread safe for ONE consumer thread!
690 if (!m_get_texture_queue.empty())
692 GetRequest<std::string, u32, u8, u8>
693 request = m_get_texture_queue.pop();
695 /*infostream<<"TextureSource::processQueue(): "
696 <<"got texture request with "
697 <<"name=\""<<request.key<<"\""
700 m_get_texture_queue.pushResult(request, generateTexture(request.key));
704 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
706 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
708 sanity_check(get_current_thread_id() == m_main_thread);
710 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
711 m_source_image_existence.set(name, true);
714 void TextureSource::rebuildImagesAndTextures()
716 JMutexAutoLock lock(m_textureinfo_cache_mutex);
718 video::IVideoDriver* driver = m_device->getVideoDriver();
719 sanity_check(driver);
722 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
723 TextureInfo *ti = &m_textureinfo_cache[i];
724 video::IImage *img = generateImage(ti->name);
726 img = Align2Npot2(img, driver);
727 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
728 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
730 // Create texture from resulting image
731 video::ITexture *t = NULL;
733 t = driver->addTexture(ti->name.c_str(), img);
734 guiScalingCache(io::path(ti->name.c_str()), driver, img);
737 video::ITexture *t_old = ti->texture;
742 m_texture_trash.push_back(t_old);
746 video::ITexture* TextureSource::generateTextureFromMesh(
747 const TextureFromMeshParams ¶ms)
749 video::IVideoDriver *driver = m_device->getVideoDriver();
750 sanity_check(driver);
753 const GLubyte* renderstr = glGetString(GL_RENDERER);
754 std::string renderer((char*) renderstr);
756 // use no render to texture hack
758 (renderer.find("Adreno") != std::string::npos) ||
759 (renderer.find("Mali") != std::string::npos) ||
760 (renderer.find("Immersion") != std::string::npos) ||
761 (renderer.find("Tegra") != std::string::npos) ||
762 g_settings->getBool("inventory_image_hack")
764 // Get a scene manager
765 scene::ISceneManager *smgr_main = m_device->getSceneManager();
766 sanity_check(smgr_main);
767 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
770 const float scaling = 0.2;
772 scene::IMeshSceneNode* meshnode =
773 smgr->addMeshSceneNode(params.mesh, NULL,
774 -1, v3f(0,0,0), v3f(0,0,0),
775 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
776 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
777 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
778 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
779 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
780 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
782 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
783 params.camera_position, params.camera_lookat);
784 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
785 camera->setProjectionMatrix(params.camera_projection_matrix, false);
787 smgr->setAmbientLight(params.ambient_light);
788 smgr->addLightSceneNode(0,
789 params.light_position,
791 params.light_radius*scaling);
793 core::dimension2d<u32> screen = driver->getScreenSize();
796 driver->beginScene(true, true, video::SColor(0,0,0,0));
797 driver->clearZBuffer();
800 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
802 irr::video::IImage* rawImage =
803 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
805 u8* pixels = static_cast<u8*>(rawImage->lock());
812 core::rect<s32> source(
813 screen.Width /2 - (screen.Width * (scaling / 2)),
814 screen.Height/2 - (screen.Height * (scaling / 2)),
815 screen.Width /2 + (screen.Width * (scaling / 2)),
816 screen.Height/2 + (screen.Height * (scaling / 2))
819 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
820 partsize.Width, partsize.Height, GL_RGBA,
821 GL_UNSIGNED_BYTE, pixels);
825 // Drop scene manager
828 unsigned int pixelcount = partsize.Width*partsize.Height;
831 for (unsigned int i=0; i < pixelcount; i++) {
849 video::IImage* inventory_image =
850 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
852 rawImage->copyToScaling(inventory_image);
855 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
857 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
858 inventory_image->drop();
861 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
865 driver->makeColorKeyTexture(rtt, v2s32(0,0));
867 if (params.delete_texture_on_shutdown)
868 m_texture_trash.push_back(rtt);
874 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
876 static bool warned = false;
879 errorstream<<"TextureSource::generateTextureFromMesh(): "
880 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
886 // Create render target texture
887 video::ITexture *rtt = driver->addRenderTargetTexture(
888 params.dim, params.rtt_texture_name.c_str(),
889 video::ECF_A8R8G8B8);
892 errorstream<<"TextureSource::generateTextureFromMesh(): "
893 <<"addRenderTargetTexture returned NULL."<<std::endl;
898 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
899 driver->removeTexture(rtt);
900 errorstream<<"TextureSource::generateTextureFromMesh(): "
901 <<"failed to set render target"<<std::endl;
905 // Get a scene manager
906 scene::ISceneManager *smgr_main = m_device->getSceneManager();
908 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
911 scene::IMeshSceneNode* meshnode =
912 smgr->addMeshSceneNode(params.mesh, NULL,
913 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
914 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
915 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
916 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
917 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
918 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
920 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
921 params.camera_position, params.camera_lookat);
922 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
923 camera->setProjectionMatrix(params.camera_projection_matrix, false);
925 smgr->setAmbientLight(params.ambient_light);
926 smgr->addLightSceneNode(0,
927 params.light_position,
929 params.light_radius);
932 driver->beginScene(true, true, video::SColor(0,0,0,0));
936 // Drop scene manager
939 // Unset render target
940 driver->setRenderTarget(0, false, true, 0);
942 if (params.delete_texture_on_shutdown)
943 m_texture_trash.push_back(rtt);
948 video::IImage* TextureSource::generateImage(const std::string &name)
954 const char separator = '^';
955 const char paren_open = '(';
956 const char paren_close = ')';
958 // Find last separator in the name
959 s32 last_separator_pos = -1;
961 for (s32 i = name.size() - 1; i >= 0; i--) {
964 if (paren_bal == 0) {
965 last_separator_pos = i;
966 i = -1; // break out of loop
970 if (paren_bal == 0) {
971 errorstream << "generateImage(): unbalanced parentheses"
972 << "(extranous '(') while generating texture \""
973 << name << "\"" << std::endl;
986 errorstream << "generateImage(): unbalanced parentheses"
987 << "(missing matching '(') while generating texture \""
988 << name << "\"" << std::endl;
993 video::IImage *baseimg = NULL;
996 If separator was found, make the base image
997 using a recursive call.
999 if (last_separator_pos != -1) {
1000 baseimg = generateImage(name.substr(0, last_separator_pos));
1004 video::IVideoDriver* driver = m_device->getVideoDriver();
1005 sanity_check(driver);
1008 Parse out the last part of the name of the image and act
1012 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1015 If this name is enclosed in parentheses, generate it
1016 and blit it onto the base image
1018 if (last_part_of_name[0] == paren_open
1019 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1020 std::string name2 = last_part_of_name.substr(1,
1021 last_part_of_name.size() - 2);
1022 video::IImage *tmp = generateImage(name2);
1024 errorstream << "generateImage(): "
1025 "Failed to generate \"" << name2 << "\""
1029 core::dimension2d<u32> dim = tmp->getDimension();
1031 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1032 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1034 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1035 // Generate image according to part of name
1036 errorstream << "generateImage(): "
1037 "Failed to generate \"" << last_part_of_name << "\""
1041 // If no resulting image, print a warning
1042 if (baseimg == NULL) {
1043 errorstream << "generateImage(): baseimg is NULL (attempted to"
1044 " create texture \"" << name << "\")" << std::endl;
1051 #include <GLES/gl.h>
1053 * Check and align image to npot2 if required by hardware
1054 * @param image image to check for npot2 alignment
1055 * @param driver driver to use for image operations
1056 * @return image or copy of image aligned to npot2
1058 video::IImage * Align2Npot2(video::IImage * image,
1059 video::IVideoDriver* driver)
1061 if (image == NULL) {
1065 core::dimension2d<u32> dim = image->getDimension();
1067 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1068 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1072 unsigned int height = npot2(dim.Height);
1073 unsigned int width = npot2(dim.Width);
1075 if ((dim.Height == height) &&
1076 (dim.Width == width)) {
1080 if (dim.Height > height) {
1084 if (dim.Width > width) {
1088 video::IImage *targetimage =
1089 driver->createImage(video::ECF_A8R8G8B8,
1090 core::dimension2d<u32>(width, height));
1092 if (targetimage != NULL) {
1093 image->copyToScaling(targetimage);
1101 bool TextureSource::generateImagePart(std::string part_of_name,
1102 video::IImage *& baseimg)
1104 video::IVideoDriver* driver = m_device->getVideoDriver();
1105 sanity_check(driver);
1107 // Stuff starting with [ are special commands
1108 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1110 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1112 image = Align2Npot2(image, driver);
1114 if (image == NULL) {
1115 if (part_of_name != "") {
1116 if (part_of_name.find("_normal.png") == std::string::npos){
1117 errorstream<<"generateImage(): Could not load image \""
1118 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1119 errorstream<<"generateImage(): Creating a dummy"
1120 <<" image for \""<<part_of_name<<"\""<<std::endl;
1122 infostream<<"generateImage(): Could not load normal map \""
1123 <<part_of_name<<"\""<<std::endl;
1124 infostream<<"generateImage(): Creating a dummy"
1125 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1129 // Just create a dummy image
1130 //core::dimension2d<u32> dim(2,2);
1131 core::dimension2d<u32> dim(1,1);
1132 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1133 sanity_check(image != NULL);
1134 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1135 image->setPixel(1,0, video::SColor(255,0,255,0));
1136 image->setPixel(0,1, video::SColor(255,0,0,255));
1137 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1138 image->setPixel(0,0, video::SColor(255,myrand()%256,
1139 myrand()%256,myrand()%256));
1140 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1141 myrand()%256,myrand()%256));
1142 image->setPixel(0,1, video::SColor(255,myrand()%256,
1143 myrand()%256,myrand()%256));
1144 image->setPixel(1,1, video::SColor(255,myrand()%256,
1145 myrand()%256,myrand()%256));*/
1148 // If base image is NULL, load as base.
1149 if (baseimg == NULL)
1151 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1153 Copy it this way to get an alpha channel.
1154 Otherwise images with alpha cannot be blitted on
1155 images that don't have alpha in the original file.
1157 core::dimension2d<u32> dim = image->getDimension();
1158 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1159 image->copyTo(baseimg);
1161 // Else blit on base.
1164 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1165 // Size of the copied area
1166 core::dimension2d<u32> dim = image->getDimension();
1167 //core::dimension2d<u32> dim(16,16);
1168 // Position to copy the blitted to in the base image
1169 core::position2d<s32> pos_to(0,0);
1170 // Position to copy the blitted from in the blitted image
1171 core::position2d<s32> pos_from(0,0);
1173 /*image->copyToWithAlpha(baseimg, pos_to,
1174 core::rect<s32>(pos_from, dim),
1175 video::SColor(255,255,255,255),
1177 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1184 // A special texture modification
1186 /*infostream<<"generateImage(): generating special "
1187 <<"modification \""<<part_of_name<<"\""
1193 Adds a cracking texture
1194 N = animation frame count, P = crack progression
1196 if (str_starts_with(part_of_name, "[crack"))
1198 if (baseimg == NULL) {
1199 errorstream<<"generateImagePart(): baseimg == NULL "
1200 <<"for part_of_name=\""<<part_of_name
1201 <<"\", cancelling."<<std::endl;
1205 // Crack image number and overlay option
1206 bool use_overlay = (part_of_name[6] == 'o');
1207 Strfnd sf(part_of_name);
1209 s32 frame_count = stoi(sf.next(":"));
1210 s32 progression = stoi(sf.next(":"));
1215 It is an image with a number of cracking stages
1218 video::IImage *img_crack = m_sourcecache.getOrLoad(
1219 "crack_anylength.png", m_device);
1221 if (img_crack && progression >= 0)
1223 draw_crack(img_crack, baseimg,
1224 use_overlay, frame_count,
1225 progression, driver);
1230 [combine:WxH:X,Y=filename:X,Y=filename2
1231 Creates a bigger texture from an amount of smaller ones
1233 else if (str_starts_with(part_of_name, "[combine"))
1235 Strfnd sf(part_of_name);
1237 u32 w0 = stoi(sf.next("x"));
1238 u32 h0 = stoi(sf.next(":"));
1239 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1240 core::dimension2d<u32> dim(w0,h0);
1241 if (baseimg == NULL) {
1242 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1243 baseimg->fill(video::SColor(0,0,0,0));
1245 while (sf.atend() == false) {
1246 u32 x = stoi(sf.next(","));
1247 u32 y = stoi(sf.next("="));
1248 std::string filename = sf.next(":");
1249 infostream<<"Adding \""<<filename
1250 <<"\" to combined ("<<x<<","<<y<<")"
1252 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1254 core::dimension2d<u32> dim = img->getDimension();
1255 infostream<<"Size "<<dim.Width
1256 <<"x"<<dim.Height<<std::endl;
1257 core::position2d<s32> pos_base(x, y);
1258 video::IImage *img2 =
1259 driver->createImage(video::ECF_A8R8G8B8, dim);
1262 /*img2->copyToWithAlpha(baseimg, pos_base,
1263 core::rect<s32>(v2s32(0,0), dim),
1264 video::SColor(255,255,255,255),
1266 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1269 errorstream << "generateImagePart(): Failed to load image \""
1270 << filename << "\" for [combine" << std::endl;
1277 else if (str_starts_with(part_of_name, "[brighten"))
1279 if (baseimg == NULL) {
1280 errorstream<<"generateImagePart(): baseimg==NULL "
1281 <<"for part_of_name=\""<<part_of_name
1282 <<"\", cancelling."<<std::endl;
1290 Make image completely opaque.
1291 Used for the leaves texture when in old leaves mode, so
1292 that the transparent parts don't look completely black
1293 when simple alpha channel is used for rendering.
1295 else if (str_starts_with(part_of_name, "[noalpha"))
1297 if (baseimg == NULL){
1298 errorstream<<"generateImagePart(): baseimg==NULL "
1299 <<"for part_of_name=\""<<part_of_name
1300 <<"\", cancelling."<<std::endl;
1304 core::dimension2d<u32> dim = baseimg->getDimension();
1306 // Set alpha to full
1307 for (u32 y=0; y<dim.Height; y++)
1308 for (u32 x=0; x<dim.Width; x++)
1310 video::SColor c = baseimg->getPixel(x,y);
1312 baseimg->setPixel(x,y,c);
1317 Convert one color to transparent.
1319 else if (str_starts_with(part_of_name, "[makealpha:"))
1321 if (baseimg == NULL) {
1322 errorstream<<"generateImagePart(): baseimg == NULL "
1323 <<"for part_of_name=\""<<part_of_name
1324 <<"\", cancelling."<<std::endl;
1328 Strfnd sf(part_of_name.substr(11));
1329 u32 r1 = stoi(sf.next(","));
1330 u32 g1 = stoi(sf.next(","));
1331 u32 b1 = stoi(sf.next(""));
1332 std::string filename = sf.next("");
1334 core::dimension2d<u32> dim = baseimg->getDimension();
1336 /*video::IImage *oldbaseimg = baseimg;
1337 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1338 oldbaseimg->copyTo(baseimg);
1339 oldbaseimg->drop();*/
1341 // Set alpha to full
1342 for (u32 y=0; y<dim.Height; y++)
1343 for (u32 x=0; x<dim.Width; x++)
1345 video::SColor c = baseimg->getPixel(x,y);
1347 u32 g = c.getGreen();
1348 u32 b = c.getBlue();
1349 if (!(r == r1 && g == g1 && b == b1))
1352 baseimg->setPixel(x,y,c);
1357 Rotates and/or flips the image.
1359 N can be a number (between 0 and 7) or a transform name.
1360 Rotations are counter-clockwise.
1362 1 R90 rotate by 90 degrees
1363 2 R180 rotate by 180 degrees
1364 3 R270 rotate by 270 degrees
1366 5 FXR90 flip X then rotate by 90 degrees
1368 7 FYR90 flip Y then rotate by 90 degrees
1370 Note: Transform names can be concatenated to produce
1371 their product (applies the first then the second).
1372 The resulting transform will be equivalent to one of the
1373 eight existing ones, though (see: dihedral group).
1375 else if (str_starts_with(part_of_name, "[transform"))
1377 if (baseimg == NULL) {
1378 errorstream<<"generateImagePart(): baseimg == NULL "
1379 <<"for part_of_name=\""<<part_of_name
1380 <<"\", cancelling."<<std::endl;
1384 u32 transform = parseImageTransform(part_of_name.substr(10));
1385 core::dimension2d<u32> dim = imageTransformDimension(
1386 transform, baseimg->getDimension());
1387 video::IImage *image = driver->createImage(
1388 baseimg->getColorFormat(), dim);
1389 sanity_check(image != NULL);
1390 imageTransform(transform, baseimg, image);
1395 [inventorycube{topimage{leftimage{rightimage
1396 In every subimage, replace ^ with &.
1397 Create an "inventory cube".
1398 NOTE: This should be used only on its own.
1399 Example (a grass block (not actually used in game):
1400 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1402 else if (str_starts_with(part_of_name, "[inventorycube"))
1404 if (baseimg != NULL){
1405 errorstream<<"generateImagePart(): baseimg != NULL "
1406 <<"for part_of_name=\""<<part_of_name
1407 <<"\", cancelling."<<std::endl;
1411 str_replace(part_of_name, '&', '^');
1412 Strfnd sf(part_of_name);
1414 std::string imagename_top = sf.next("{");
1415 std::string imagename_left = sf.next("{");
1416 std::string imagename_right = sf.next("{");
1418 // Generate images for the faces of the cube
1419 video::IImage *img_top = generateImage(imagename_top);
1420 video::IImage *img_left = generateImage(imagename_left);
1421 video::IImage *img_right = generateImage(imagename_right);
1423 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1424 errorstream << "generateImagePart(): Failed to create textures"
1425 << " for inventorycube \"" << part_of_name << "\""
1427 baseimg = generateImage(imagename_top);
1432 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1433 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1435 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1436 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1438 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1439 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1442 // Create textures from images
1443 video::ITexture *texture_top = driver->addTexture(
1444 (imagename_top + "__temp__").c_str(), img_top);
1445 video::ITexture *texture_left = driver->addTexture(
1446 (imagename_left + "__temp__").c_str(), img_left);
1447 video::ITexture *texture_right = driver->addTexture(
1448 (imagename_right + "__temp__").c_str(), img_right);
1449 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1457 Draw a cube mesh into a render target texture
1459 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1460 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1461 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1462 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1463 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1464 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1465 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1466 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1468 TextureFromMeshParams params;
1470 params.dim.set(64, 64);
1471 params.rtt_texture_name = part_of_name + "_RTT";
1472 // We will delete the rtt texture ourselves
1473 params.delete_texture_on_shutdown = false;
1474 params.camera_position.set(0, 1.0, -1.5);
1475 params.camera_position.rotateXZBy(45);
1476 params.camera_lookat.set(0, 0, 0);
1477 // Set orthogonal projection
1478 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1479 1.65, 1.65, 0, 100);
1481 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1482 params.light_position.set(10, 100, -50);
1483 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1484 params.light_radius = 1000;
1486 video::ITexture *rtt = generateTextureFromMesh(params);
1492 driver->removeTexture(texture_top);
1493 driver->removeTexture(texture_left);
1494 driver->removeTexture(texture_right);
1497 baseimg = generateImage(imagename_top);
1501 // Create image of render target
1502 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1503 FATAL_ERROR_IF(!image, "Could not create image of render target");
1506 driver->removeTexture(rtt);
1508 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1511 image->copyTo(baseimg);
1516 [lowpart:percent:filename
1517 Adds the lower part of a texture
1519 else if (str_starts_with(part_of_name, "[lowpart:"))
1521 Strfnd sf(part_of_name);
1523 u32 percent = stoi(sf.next(":"));
1524 std::string filename = sf.next(":");
1525 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1527 if (baseimg == NULL)
1528 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1529 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1532 core::dimension2d<u32> dim = img->getDimension();
1533 core::position2d<s32> pos_base(0, 0);
1534 video::IImage *img2 =
1535 driver->createImage(video::ECF_A8R8G8B8, dim);
1538 core::position2d<s32> clippos(0, 0);
1539 clippos.Y = dim.Height * (100-percent) / 100;
1540 core::dimension2d<u32> clipdim = dim;
1541 clipdim.Height = clipdim.Height * percent / 100 + 1;
1542 core::rect<s32> cliprect(clippos, clipdim);
1543 img2->copyToWithAlpha(baseimg, pos_base,
1544 core::rect<s32>(v2s32(0,0), dim),
1545 video::SColor(255,255,255,255),
1552 Crops a frame of a vertical animation.
1553 N = frame count, I = frame index
1555 else if (str_starts_with(part_of_name, "[verticalframe:"))
1557 Strfnd sf(part_of_name);
1559 u32 frame_count = stoi(sf.next(":"));
1560 u32 frame_index = stoi(sf.next(":"));
1562 if (baseimg == NULL){
1563 errorstream<<"generateImagePart(): baseimg != NULL "
1564 <<"for part_of_name=\""<<part_of_name
1565 <<"\", cancelling."<<std::endl;
1569 v2u32 frame_size = baseimg->getDimension();
1570 frame_size.Y /= frame_count;
1572 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1575 errorstream<<"generateImagePart(): Could not create image "
1576 <<"for part_of_name=\""<<part_of_name
1577 <<"\", cancelling."<<std::endl;
1581 // Fill target image with transparency
1582 img->fill(video::SColor(0,0,0,0));
1584 core::dimension2d<u32> dim = frame_size;
1585 core::position2d<s32> pos_dst(0, 0);
1586 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1587 baseimg->copyToWithAlpha(img, pos_dst,
1588 core::rect<s32>(pos_src, dim),
1589 video::SColor(255,255,255,255),
1597 Applies a mask to an image
1599 else if (str_starts_with(part_of_name, "[mask:"))
1601 if (baseimg == NULL) {
1602 errorstream << "generateImage(): baseimg == NULL "
1603 << "for part_of_name=\"" << part_of_name
1604 << "\", cancelling." << std::endl;
1607 Strfnd sf(part_of_name);
1609 std::string filename = sf.next(":");
1611 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1613 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1614 img->getDimension());
1616 errorstream << "generateImage(): Failed to load \""
1617 << filename << "\".";
1622 Overlays image with given color
1623 color = color as ColorString
1625 else if (str_starts_with(part_of_name, "[colorize:"))
1627 Strfnd sf(part_of_name);
1629 std::string color_str = sf.next(":");
1630 std::string ratio_str = sf.next(":");
1632 if (baseimg == NULL) {
1633 errorstream << "generateImagePart(): baseimg != NULL "
1634 << "for part_of_name=\"" << part_of_name
1635 << "\", cancelling." << std::endl;
1639 video::SColor color;
1642 if (!parseColorString(color_str, color, false))
1645 if (is_number(ratio_str))
1646 ratio = mystoi(ratio_str, 0, 255);
1648 core::dimension2d<u32> dim = baseimg->getDimension();
1649 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1652 errorstream << "generateImagePart(): Could not create image "
1653 << "for part_of_name=\"" << part_of_name
1654 << "\", cancelling." << std::endl;
1658 img->fill(video::SColor(color));
1659 // Overlay the colored image
1660 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1663 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1665 // Apply the "clean transparent" filter, if configured.
1666 if (g_settings->getBool("texture_clean_transparent"))
1667 imageCleanTransparent(baseimg, 127);
1669 /* Upscale textures to user's requested minimum size. This is a trick to make
1670 * filters look as good on low-res textures as on high-res ones, by making
1671 * low-res textures BECOME high-res ones. This is helpful for worlds that
1672 * mix high- and low-res textures, or for mods with least-common-denominator
1673 * textures that don't have the resources to offer high-res alternatives.
1675 s32 scaleto = g_settings->getS32("texture_min_size");
1677 const core::dimension2d<u32> dim = baseimg->getDimension();
1679 /* Calculate scaling needed to make the shortest texture dimension
1680 * equal to the target minimum. If e.g. this is a vertical frames
1681 * animation, the short dimension will be the real size.
1683 u32 xscale = scaleto / dim.Width;
1684 u32 yscale = scaleto / dim.Height;
1685 u32 scale = (xscale > yscale) ? xscale : yscale;
1687 // Never downscale; only scale up by 2x or more.
1689 u32 w = scale * dim.Width;
1690 u32 h = scale * dim.Height;
1691 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1692 video::IImage *newimg = driver->createImage(
1693 baseimg->getColorFormat(), newdim);
1694 baseimg->copyToScaling(newimg);
1702 errorstream << "generateImagePart(): Invalid "
1703 " modification: \"" << part_of_name << "\"" << std::endl;
1711 Draw an image on top of an another one, using the alpha channel of the
1714 This exists because IImage::copyToWithAlpha() doesn't seem to always
1717 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1718 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1720 for (u32 y0=0; y0<size.Y; y0++)
1721 for (u32 x0=0; x0<size.X; x0++)
1723 s32 src_x = src_pos.X + x0;
1724 s32 src_y = src_pos.Y + y0;
1725 s32 dst_x = dst_pos.X + x0;
1726 s32 dst_y = dst_pos.Y + y0;
1727 video::SColor src_c = src->getPixel(src_x, src_y);
1728 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1729 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1730 dst->setPixel(dst_x, dst_y, dst_c);
1735 Draw an image on top of an another one, using the alpha channel of the
1736 source image; only modify fully opaque pixels in destinaion
1738 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1739 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1741 for (u32 y0=0; y0<size.Y; y0++)
1742 for (u32 x0=0; x0<size.X; x0++)
1744 s32 src_x = src_pos.X + x0;
1745 s32 src_y = src_pos.Y + y0;
1746 s32 dst_x = dst_pos.X + x0;
1747 s32 dst_y = dst_pos.Y + y0;
1748 video::SColor src_c = src->getPixel(src_x, src_y);
1749 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1750 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1752 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1753 dst->setPixel(dst_x, dst_y, dst_c);
1759 Draw an image on top of an another one, using the specified ratio
1760 modify all partially-opaque pixels in the destination.
1762 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1763 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1765 for (u32 y0 = 0; y0 < size.Y; y0++)
1766 for (u32 x0 = 0; x0 < size.X; x0++)
1768 s32 src_x = src_pos.X + x0;
1769 s32 src_y = src_pos.Y + y0;
1770 s32 dst_x = dst_pos.X + x0;
1771 s32 dst_y = dst_pos.Y + y0;
1772 video::SColor src_c = src->getPixel(src_x, src_y);
1773 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1774 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1777 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1779 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1780 dst->setPixel(dst_x, dst_y, dst_c);
1786 Apply mask to destination
1788 static void apply_mask(video::IImage *mask, video::IImage *dst,
1789 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1791 for (u32 y0 = 0; y0 < size.Y; y0++) {
1792 for (u32 x0 = 0; x0 < size.X; x0++) {
1793 s32 mask_x = x0 + mask_pos.X;
1794 s32 mask_y = y0 + mask_pos.Y;
1795 s32 dst_x = x0 + dst_pos.X;
1796 s32 dst_y = y0 + dst_pos.Y;
1797 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1798 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1799 dst_c.color &= mask_c.color;
1800 dst->setPixel(dst_x, dst_y, dst_c);
1805 static void draw_crack(video::IImage *crack, video::IImage *dst,
1806 bool use_overlay, s32 frame_count, s32 progression,
1807 video::IVideoDriver *driver)
1809 // Dimension of destination image
1810 core::dimension2d<u32> dim_dst = dst->getDimension();
1811 // Dimension of original image
1812 core::dimension2d<u32> dim_crack = crack->getDimension();
1813 // Count of crack stages
1814 s32 crack_count = dim_crack.Height / dim_crack.Width;
1815 // Limit frame_count
1816 if (frame_count > (s32) dim_dst.Height)
1817 frame_count = dim_dst.Height;
1818 if (frame_count < 1)
1820 // Limit progression
1821 if (progression > crack_count-1)
1822 progression = crack_count-1;
1823 // Dimension of a single crack stage
1824 core::dimension2d<u32> dim_crack_cropped(
1828 // Dimension of the scaled crack stage,
1829 // which is the same as the dimension of a single destination frame
1830 core::dimension2d<u32> dim_crack_scaled(
1832 dim_dst.Height / frame_count
1834 // Create cropped and scaled crack images
1835 video::IImage *crack_cropped = driver->createImage(
1836 video::ECF_A8R8G8B8, dim_crack_cropped);
1837 video::IImage *crack_scaled = driver->createImage(
1838 video::ECF_A8R8G8B8, dim_crack_scaled);
1840 if (crack_cropped && crack_scaled)
1843 v2s32 pos_crack(0, progression*dim_crack.Width);
1844 crack->copyTo(crack_cropped,
1846 core::rect<s32>(pos_crack, dim_crack_cropped));
1847 // Scale crack image by copying
1848 crack_cropped->copyToScaling(crack_scaled);
1849 // Copy or overlay crack image onto each frame
1850 for (s32 i = 0; i < frame_count; ++i)
1852 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1855 blit_with_alpha_overlay(crack_scaled, dst,
1856 v2s32(0,0), dst_pos,
1861 blit_with_alpha(crack_scaled, dst,
1862 v2s32(0,0), dst_pos,
1869 crack_scaled->drop();
1872 crack_cropped->drop();
1875 void brighten(video::IImage *image)
1880 core::dimension2d<u32> dim = image->getDimension();
1882 for (u32 y=0; y<dim.Height; y++)
1883 for (u32 x=0; x<dim.Width; x++)
1885 video::SColor c = image->getPixel(x,y);
1886 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1887 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1888 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1889 image->setPixel(x,y,c);
1893 u32 parseImageTransform(const std::string& s)
1895 int total_transform = 0;
1897 std::string transform_names[8];
1898 transform_names[0] = "i";
1899 transform_names[1] = "r90";
1900 transform_names[2] = "r180";
1901 transform_names[3] = "r270";
1902 transform_names[4] = "fx";
1903 transform_names[6] = "fy";
1905 std::size_t pos = 0;
1906 while(pos < s.size())
1909 for (int i = 0; i <= 7; ++i)
1911 const std::string &name_i = transform_names[i];
1913 if (s[pos] == ('0' + i))
1919 else if (!(name_i.empty()) &&
1920 lowercase(s.substr(pos, name_i.size())) == name_i)
1923 pos += name_i.size();
1930 // Multiply total_transform and transform in the group D4
1933 new_total = (transform + total_transform) % 4;
1935 new_total = (transform - total_transform + 8) % 4;
1936 if ((transform >= 4) ^ (total_transform >= 4))
1939 total_transform = new_total;
1941 return total_transform;
1944 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1946 if (transform % 2 == 0)
1949 return core::dimension2d<u32>(dim.Height, dim.Width);
1952 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1954 if (src == NULL || dst == NULL)
1957 core::dimension2d<u32> dstdim = dst->getDimension();
1960 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1961 assert(transform <= 7);
1964 Compute the transformation from source coordinates (sx,sy)
1965 to destination coordinates (dx,dy).
1969 if (transform == 0) // identity
1970 sxn = 0, syn = 2; // sx = dx, sy = dy
1971 else if (transform == 1) // rotate by 90 degrees ccw
1972 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1973 else if (transform == 2) // rotate by 180 degrees
1974 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1975 else if (transform == 3) // rotate by 270 degrees ccw
1976 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1977 else if (transform == 4) // flip x
1978 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1979 else if (transform == 5) // flip x then rotate by 90 degrees ccw
1980 sxn = 2, syn = 0; // sx = dy, sy = dx
1981 else if (transform == 6) // flip y
1982 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1983 else if (transform == 7) // flip y then rotate by 90 degrees ccw
1984 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1986 for (u32 dy=0; dy<dstdim.Height; dy++)
1987 for (u32 dx=0; dx<dstdim.Width; dx++)
1989 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1990 u32 sx = entries[sxn];
1991 u32 sy = entries[syn];
1992 video::SColor c = src->getPixel(sx,sy);
1993 dst->setPixel(dx,dy,c);
1997 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1999 if (isKnownSourceImage("override_normal.png"))
2000 return getTexture("override_normal.png");
2001 std::string fname_base = name;
2002 std::string normal_ext = "_normal.png";
2003 size_t pos = fname_base.find(".");
2004 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2005 if (isKnownSourceImage(fname_normal)) {
2006 // look for image extension and replace it
2008 while ((i = fname_base.find(".", i)) != std::string::npos) {
2009 fname_base.replace(i, 4, normal_ext);
2010 i += normal_ext.length();
2012 return getTexture(fname_base);
2017 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2019 video::IVideoDriver *driver = m_device->getVideoDriver();
2020 video::SColor c(0, 0, 0, 0);
2021 video::ITexture *texture = getTexture(name);
2022 video::IImage *image = driver->createImage(texture,
2023 core::position2d<s32>(0, 0),
2024 texture->getOriginalSize());
2029 core::dimension2d<u32> dim = image->getDimension();
2032 step = dim.Width / 16;
2033 for (u16 x = 0; x < dim.Width; x += step) {
2034 for (u16 y = 0; y < dim.Width; y += step) {
2035 c = image->getPixel(x,y);
2036 if (c.getAlpha() > 0) {
2046 c.setRed(tR / total);
2047 c.setGreen(tG / total);
2048 c.setBlue(tB / total);
2055 video::ITexture *TextureSource::getShaderFlagsTexture(
2056 bool normalmap_present, bool tileable_vertical, bool tileable_horizontal)
2058 std::string tname = "__shaderFlagsTexture";
2059 tname += normalmap_present ? "1" : "0";
2060 tname += tileable_horizontal ? "1" : "0";
2061 tname += tileable_vertical ? "1" : "0";
2063 if (isKnownSourceImage(tname)) {
2064 return getTexture(tname);
2066 video::IVideoDriver *driver = m_device->getVideoDriver();
2067 video::IImage *flags_image = driver->createImage(
2068 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2069 sanity_check(flags_image != NULL);
2072 normalmap_present ? 255 : 0,
2073 tileable_horizontal ? 255 : 0,
2074 tileable_vertical ? 255 : 0);
2075 flags_image->setPixel(0, 0, c);
2076 insertSourceImage(tname, flags_image);
2077 flags_image->drop();
2078 return getTexture(tname);