3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
29 #include "main.h" // for g_settings
36 #include "util/string.h" // for parseColorString()
43 A cache from texture name to texture path
45 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
48 Replaces the filename extension.
50 std::string image = "a/image.png"
51 replace_ext(image, "jpg")
52 -> image = "a/image.jpg"
53 Returns true on success.
55 static bool replace_ext(std::string &path, const char *ext)
59 // Find place of last dot, fail if \ or / found.
61 for (s32 i=path.size()-1; i>=0; i--)
69 if (path[i] == '\\' || path[i] == '/')
72 // If not found, return an empty string
75 // Else make the new path
76 path = path.substr(0, last_dot_i+1) + ext;
81 Find out the full path of an image by trying different filename
86 std::string getImagePath(std::string path)
88 // A NULL-ended list of possible image extensions
89 const char *extensions[] = {
90 "png", "jpg", "bmp", "tga",
91 "pcx", "ppm", "psd", "wal", "rgb",
94 // If there is no extension, add one
95 if (removeStringEnd(path, extensions) == "")
97 // Check paths until something is found to exist
98 const char **ext = extensions;
100 bool r = replace_ext(path, *ext);
103 if (fs::PathExists(path))
106 while((++ext) != NULL);
112 Gets the path to a texture by first checking if the texture exists
113 in texture_path and if not, using the data path.
115 Checks all supported extensions by replacing the original extension.
117 If not found, returns "".
119 Utilizes a thread-safe cache.
121 std::string getTexturePath(const std::string &filename)
123 std::string fullpath = "";
127 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
132 Check from texture_path
134 std::string texture_path = g_settings->get("texture_path");
135 if (texture_path != "")
137 std::string testpath = texture_path + DIR_DELIM + filename;
138 // Check all filename extensions. Returns "" if not found.
139 fullpath = getImagePath(testpath);
143 Check from default data directory
147 std::string base_path = porting::path_share + DIR_DELIM + "textures"
148 + DIR_DELIM + "base" + DIR_DELIM + "pack";
149 std::string testpath = base_path + DIR_DELIM + filename;
150 // Check all filename extensions. Returns "" if not found.
151 fullpath = getImagePath(testpath);
154 // Add to cache (also an empty result is cached)
155 g_texturename_to_path_cache.set(filename, fullpath);
161 void clearTextureNameCache()
163 g_texturename_to_path_cache.clear();
167 Stores internal information about a texture.
173 video::ITexture *texture;
176 const std::string &name_,
177 video::ITexture *texture_=NULL
186 SourceImageCache: A cache used for storing source images.
189 class SourceImageCache
192 ~SourceImageCache() {
193 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
194 iter != m_images.end(); iter++) {
195 iter->second->drop();
199 void insert(const std::string &name, video::IImage *img,
200 bool prefer_local, video::IVideoDriver *driver)
202 assert(img); // Pre-condition
204 std::map<std::string, video::IImage*>::iterator n;
205 n = m_images.find(name);
206 if (n != m_images.end()){
211 video::IImage* toadd = img;
212 bool need_to_grab = true;
214 // Try to use local texture instead if asked to
216 std::string path = getTexturePath(name);
218 video::IImage *img2 = driver->createImageFromFile(path.c_str());
221 need_to_grab = false;
226 /* Apply the "clean transparent" filter to textures, removing borders on transparent textures.
227 * PNG optimizers discard RGB values of fully-transparent pixels, but filters may expose the
228 * replacement colors at borders by blending to them; this filter compensates for that by
229 * filling in those RGB values from nearby pixels.
231 if (g_settings->getBool("texture_clean_transparent")) {
232 const core::dimension2d<u32> dim = toadd->getDimension();
234 // Walk each pixel looking for ones that will show as transparent.
235 for (u32 ctrx = 0; ctrx < dim.Width; ctrx++)
236 for (u32 ctry = 0; ctry < dim.Height; ctry++) {
237 irr::video::SColor c = toadd->getPixel(ctrx, ctry);
238 if (c.getAlpha() > 127)
241 // Sample size and total weighted r, g, b values.
242 u32 ss = 0, sr = 0, sg = 0, sb = 0;
244 // Walk each neighbor pixel (clipped to image bounds).
245 for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
246 sx <= (ctrx + 1) && sx < dim.Width; sx++)
247 for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
248 sy <= (ctry + 1) && sy < dim.Height; sy++) {
250 // Ignore the center pixel (its RGB is already
251 // presumed meaningless).
252 if ((sx == ctrx) && (sy == ctry))
255 // Ignore other nearby pixels that would be
256 // transparent upon display.
257 irr::video::SColor d = toadd->getPixel(sx, sy);
258 if(d.getAlpha() < 128)
261 // Add one weighted sample.
268 // If we found any neighbor RGB data, set pixel to average
269 // weighted by alpha.
274 toadd->setPixel(ctrx, ctry, c);
281 m_images[name] = toadd;
283 video::IImage* get(const std::string &name)
285 std::map<std::string, video::IImage*>::iterator n;
286 n = m_images.find(name);
287 if (n != m_images.end())
291 // Primarily fetches from cache, secondarily tries to read from filesystem
292 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
294 std::map<std::string, video::IImage*>::iterator n;
295 n = m_images.find(name);
296 if (n != m_images.end()){
297 n->second->grab(); // Grab for caller
300 video::IVideoDriver* driver = device->getVideoDriver();
301 std::string path = getTexturePath(name);
303 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
304 <<name<<"\""<<std::endl;
307 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
309 video::IImage *img = driver->createImageFromFile(path.c_str());
312 m_images[name] = img;
313 img->grab(); // Grab for caller
318 std::map<std::string, video::IImage*> m_images;
325 class TextureSource : public IWritableTextureSource
328 TextureSource(IrrlichtDevice *device);
329 virtual ~TextureSource();
333 Now, assume a texture with the id 1 exists, and has the name
334 "stone.png^mineral1".
335 Then a random thread calls getTextureId for a texture called
336 "stone.png^mineral1^crack0".
337 ...Now, WTF should happen? Well:
338 - getTextureId strips off stuff recursively from the end until
339 the remaining part is found, or nothing is left when
340 something is stripped out
342 But it is slow to search for textures by names and modify them
344 - ContentFeatures is made to contain ids for the basic plain
346 - Crack textures can be slow by themselves, but the framework
350 - Assume a texture with the id 1 exists, and has the name
351 "stone.png^mineral_coal.png".
352 - Now getNodeTile() stumbles upon a node which uses
353 texture id 1, and determines that MATERIAL_FLAG_CRACK
354 must be applied to the tile
355 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
356 has received the current crack level 0 from the client. It
357 finds out the name of the texture with getTextureName(1),
358 appends "^crack0" to it and gets a new texture id with
359 getTextureId("stone.png^mineral_coal.png^crack0").
364 Gets a texture id from cache or
365 - if main thread, generates the texture, adds to cache and returns id.
366 - if other thread, adds to request queue and waits for main thread.
368 The id 0 points to a NULL texture. It is returned in case of error.
370 u32 getTextureId(const std::string &name);
372 // Finds out the name of a cached texture.
373 std::string getTextureName(u32 id);
376 If texture specified by the name pointed by the id doesn't
377 exist, create it, then return the cached texture.
379 Can be called from any thread. If called from some other thread
380 and not found in cache, the call is queued to the main thread
383 video::ITexture* getTexture(u32 id);
385 video::ITexture* getTexture(const std::string &name, u32 *id);
388 Get a texture specifically intended for mesh
389 application, i.e. not HUD, compositing, or other 2D
390 use. This texture may be a different size and may
391 have had additional filters applied.
393 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
395 // Returns a pointer to the irrlicht device
396 virtual IrrlichtDevice* getDevice()
401 bool isKnownSourceImage(const std::string &name)
403 bool is_known = false;
404 bool cache_found = m_source_image_existence.get(name, &is_known);
407 // Not found in cache; find out if a local file exists
408 is_known = (getTexturePath(name) != "");
409 m_source_image_existence.set(name, is_known);
413 // Processes queued texture requests from other threads.
414 // Shall be called from the main thread.
417 // Insert an image into the cache without touching the filesystem.
418 // Shall be called from the main thread.
419 void insertSourceImage(const std::string &name, video::IImage *img);
421 // Rebuild images and textures from the current set of source images
422 // Shall be called from the main thread.
423 void rebuildImagesAndTextures();
425 // Render a mesh to a texture.
426 // Returns NULL if render-to-texture failed.
427 // Shall be called from the main thread.
428 video::ITexture* generateTextureFromMesh(
429 const TextureFromMeshParams ¶ms);
431 // Generates an image from a full string like
432 // "stone.png^mineral_coal.png^[crack:1:0".
433 // Shall be called from the main thread.
434 video::IImage* generateImage(const std::string &name);
436 video::ITexture* getNormalTexture(const std::string &name);
439 // The id of the thread that is allowed to use irrlicht directly
440 threadid_t m_main_thread;
441 // The irrlicht device
442 IrrlichtDevice *m_device;
444 // Cache of source images
445 // This should be only accessed from the main thread
446 SourceImageCache m_sourcecache;
448 // Generate a texture
449 u32 generateTexture(const std::string &name);
451 // Generate image based on a string like "stone.png" or "[crack:1:0".
452 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
453 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
455 // Thread-safe cache of what source images are known (true = known)
456 MutexedMap<std::string, bool> m_source_image_existence;
458 // A texture id is index in this array.
459 // The first position contains a NULL texture.
460 std::vector<TextureInfo> m_textureinfo_cache;
461 // Maps a texture name to an index in the former.
462 std::map<std::string, u32> m_name_to_id;
463 // The two former containers are behind this mutex
464 JMutex m_textureinfo_cache_mutex;
466 // Queued texture fetches (to be processed by the main thread)
467 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
469 // Textures that have been overwritten with other ones
470 // but can't be deleted because the ITexture* might still be used
471 std::vector<video::ITexture*> m_texture_trash;
473 // Cached settings needed for making textures from meshes
474 bool m_setting_trilinear_filter;
475 bool m_setting_bilinear_filter;
476 bool m_setting_anisotropic_filter;
479 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
481 return new TextureSource(device);
484 TextureSource::TextureSource(IrrlichtDevice *device):
487 assert(m_device); // Pre-condition
489 m_main_thread = get_current_thread_id();
491 // Add a NULL TextureInfo as the first index, named ""
492 m_textureinfo_cache.push_back(TextureInfo(""));
493 m_name_to_id[""] = 0;
495 // Cache some settings
496 // Note: Since this is only done once, the game must be restarted
497 // for these settings to take effect
498 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
499 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
500 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
503 TextureSource::~TextureSource()
505 video::IVideoDriver* driver = m_device->getVideoDriver();
507 unsigned int textures_before = driver->getTextureCount();
509 for (std::vector<TextureInfo>::iterator iter =
510 m_textureinfo_cache.begin();
511 iter != m_textureinfo_cache.end(); iter++)
515 driver->removeTexture(iter->texture);
517 m_textureinfo_cache.clear();
519 for (std::vector<video::ITexture*>::iterator iter =
520 m_texture_trash.begin(); iter != m_texture_trash.end();
522 video::ITexture *t = *iter;
524 //cleanup trashed texture
525 driver->removeTexture(t);
528 infostream << "~TextureSource() "<< textures_before << "/"
529 << driver->getTextureCount() << std::endl;
532 u32 TextureSource::getTextureId(const std::string &name)
534 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
538 See if texture already exists
540 JMutexAutoLock lock(m_textureinfo_cache_mutex);
541 std::map<std::string, u32>::iterator n;
542 n = m_name_to_id.find(name);
543 if (n != m_name_to_id.end())
552 if (get_current_thread_id() == m_main_thread)
554 return generateTexture(name);
558 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
560 // We're gonna ask the result to be put into here
561 static ResultQueue<std::string, u32, u8, u8> result_queue;
563 // Throw a request in
564 m_get_texture_queue.add(name, 0, 0, &result_queue);
566 /*infostream<<"Waiting for texture from main thread, name=\""
567 <<name<<"\""<<std::endl;*/
572 // Wait result for a second
573 GetResult<std::string, u32, u8, u8>
574 result = result_queue.pop_front(1000);
576 if (result.key == name) {
581 catch(ItemNotFoundException &e)
583 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
588 infostream<<"getTextureId(): Failed"<<std::endl;
593 // Draw an image on top of an another one, using the alpha channel of the
595 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
596 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
598 // Like blit_with_alpha, but only modifies destination pixels that
600 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
601 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
603 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
604 // and modifies any destination pixels that are not fully transparent
605 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
606 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
608 // Apply a mask to an image
609 static void apply_mask(video::IImage *mask, video::IImage *dst,
610 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
612 // Draw or overlay a crack
613 static void draw_crack(video::IImage *crack, video::IImage *dst,
614 bool use_overlay, s32 frame_count, s32 progression,
615 video::IVideoDriver *driver);
618 void brighten(video::IImage *image);
619 // Parse a transform name
620 u32 parseImageTransform(const std::string& s);
621 // Apply transform to image dimension
622 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
623 // Apply transform to image data
624 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
627 This method generates all the textures
629 u32 TextureSource::generateTexture(const std::string &name)
631 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
633 // Empty name means texture 0
635 infostream<<"generateTexture(): name is empty"<<std::endl;
641 See if texture already exists
643 JMutexAutoLock lock(m_textureinfo_cache_mutex);
644 std::map<std::string, u32>::iterator n;
645 n = m_name_to_id.find(name);
646 if (n != m_name_to_id.end()) {
652 Calling only allowed from main thread
654 if (get_current_thread_id() != m_main_thread) {
655 errorstream<<"TextureSource::generateTexture() "
656 "called not from main thread"<<std::endl;
660 video::IVideoDriver *driver = m_device->getVideoDriver();
661 sanity_check(driver);
663 video::IImage *img = generateImage(name);
665 video::ITexture *tex = NULL;
669 img = Align2Npot2(img, driver);
671 // Create texture from resulting image
672 tex = driver->addTexture(name.c_str(), img);
677 Add texture to caches (add NULL textures too)
680 JMutexAutoLock lock(m_textureinfo_cache_mutex);
682 u32 id = m_textureinfo_cache.size();
683 TextureInfo ti(name, tex);
684 m_textureinfo_cache.push_back(ti);
685 m_name_to_id[name] = id;
690 std::string TextureSource::getTextureName(u32 id)
692 JMutexAutoLock lock(m_textureinfo_cache_mutex);
694 if (id >= m_textureinfo_cache.size())
696 errorstream<<"TextureSource::getTextureName(): id="<<id
697 <<" >= m_textureinfo_cache.size()="
698 <<m_textureinfo_cache.size()<<std::endl;
702 return m_textureinfo_cache[id].name;
705 video::ITexture* TextureSource::getTexture(u32 id)
707 JMutexAutoLock lock(m_textureinfo_cache_mutex);
709 if (id >= m_textureinfo_cache.size())
712 return m_textureinfo_cache[id].texture;
715 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
717 u32 actual_id = getTextureId(name);
721 return getTexture(actual_id);
724 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
726 return getTexture(name + "^[autoupscaleformesh", id);
729 void TextureSource::processQueue()
734 //NOTE this is only thread safe for ONE consumer thread!
735 if (!m_get_texture_queue.empty())
737 GetRequest<std::string, u32, u8, u8>
738 request = m_get_texture_queue.pop();
740 /*infostream<<"TextureSource::processQueue(): "
741 <<"got texture request with "
742 <<"name=\""<<request.key<<"\""
745 m_get_texture_queue.pushResult(request, generateTexture(request.key));
749 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
751 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
753 sanity_check(get_current_thread_id() == m_main_thread);
755 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
756 m_source_image_existence.set(name, true);
759 void TextureSource::rebuildImagesAndTextures()
761 JMutexAutoLock lock(m_textureinfo_cache_mutex);
763 video::IVideoDriver* driver = m_device->getVideoDriver();
764 sanity_check(driver);
767 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
768 TextureInfo *ti = &m_textureinfo_cache[i];
769 video::IImage *img = generateImage(ti->name);
771 img = Align2Npot2(img, driver);
772 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
773 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
775 // Create texture from resulting image
776 video::ITexture *t = NULL;
778 t = driver->addTexture(ti->name.c_str(), img);
781 video::ITexture *t_old = ti->texture;
786 m_texture_trash.push_back(t_old);
790 video::ITexture* TextureSource::generateTextureFromMesh(
791 const TextureFromMeshParams ¶ms)
793 video::IVideoDriver *driver = m_device->getVideoDriver();
794 sanity_check(driver);
797 const GLubyte* renderstr = glGetString(GL_RENDERER);
798 std::string renderer((char*) renderstr);
800 // use no render to texture hack
802 (renderer.find("Adreno") != std::string::npos) ||
803 (renderer.find("Mali") != std::string::npos) ||
804 (renderer.find("Immersion") != std::string::npos) ||
805 (renderer.find("Tegra") != std::string::npos) ||
806 g_settings->getBool("inventory_image_hack")
808 // Get a scene manager
809 scene::ISceneManager *smgr_main = m_device->getSceneManager();
810 sanity_check(smgr_main);
811 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
814 const float scaling = 0.2;
816 scene::IMeshSceneNode* meshnode =
817 smgr->addMeshSceneNode(params.mesh, NULL,
818 -1, v3f(0,0,0), v3f(0,0,0),
819 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
820 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
821 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
822 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
823 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
824 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
826 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
827 params.camera_position, params.camera_lookat);
828 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
829 camera->setProjectionMatrix(params.camera_projection_matrix, false);
831 smgr->setAmbientLight(params.ambient_light);
832 smgr->addLightSceneNode(0,
833 params.light_position,
835 params.light_radius*scaling);
837 core::dimension2d<u32> screen = driver->getScreenSize();
840 driver->beginScene(true, true, video::SColor(0,0,0,0));
841 driver->clearZBuffer();
844 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
846 irr::video::IImage* rawImage =
847 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
849 u8* pixels = static_cast<u8*>(rawImage->lock());
856 core::rect<s32> source(
857 screen.Width /2 - (screen.Width * (scaling / 2)),
858 screen.Height/2 - (screen.Height * (scaling / 2)),
859 screen.Width /2 + (screen.Width * (scaling / 2)),
860 screen.Height/2 + (screen.Height * (scaling / 2))
863 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
864 partsize.Width, partsize.Height, GL_RGBA,
865 GL_UNSIGNED_BYTE, pixels);
869 // Drop scene manager
872 unsigned int pixelcount = partsize.Width*partsize.Height;
875 for (unsigned int i=0; i < pixelcount; i++) {
893 video::IImage* inventory_image =
894 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
896 rawImage->copyToScaling(inventory_image);
899 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
900 inventory_image->drop();
903 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
907 driver->makeColorKeyTexture(rtt, v2s32(0,0));
909 if (params.delete_texture_on_shutdown)
910 m_texture_trash.push_back(rtt);
916 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
918 static bool warned = false;
921 errorstream<<"TextureSource::generateTextureFromMesh(): "
922 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
928 // Create render target texture
929 video::ITexture *rtt = driver->addRenderTargetTexture(
930 params.dim, params.rtt_texture_name.c_str(),
931 video::ECF_A8R8G8B8);
934 errorstream<<"TextureSource::generateTextureFromMesh(): "
935 <<"addRenderTargetTexture returned NULL."<<std::endl;
940 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
941 driver->removeTexture(rtt);
942 errorstream<<"TextureSource::generateTextureFromMesh(): "
943 <<"failed to set render target"<<std::endl;
947 // Get a scene manager
948 scene::ISceneManager *smgr_main = m_device->getSceneManager();
950 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
953 scene::IMeshSceneNode* meshnode =
954 smgr->addMeshSceneNode(params.mesh, NULL,
955 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
956 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
957 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
958 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
959 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
960 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
962 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
963 params.camera_position, params.camera_lookat);
964 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
965 camera->setProjectionMatrix(params.camera_projection_matrix, false);
967 smgr->setAmbientLight(params.ambient_light);
968 smgr->addLightSceneNode(0,
969 params.light_position,
971 params.light_radius);
974 driver->beginScene(true, true, video::SColor(0,0,0,0));
978 // Drop scene manager
981 // Unset render target
982 driver->setRenderTarget(0, false, true, 0);
984 if (params.delete_texture_on_shutdown)
985 m_texture_trash.push_back(rtt);
990 video::IImage* TextureSource::generateImage(const std::string &name)
996 const char separator = '^';
997 const char paren_open = '(';
998 const char paren_close = ')';
1000 // Find last separator in the name
1001 s32 last_separator_pos = -1;
1003 for (s32 i = name.size() - 1; i >= 0; i--) {
1006 if (paren_bal == 0) {
1007 last_separator_pos = i;
1008 i = -1; // break out of loop
1012 if (paren_bal == 0) {
1013 errorstream << "generateImage(): unbalanced parentheses"
1014 << "(extranous '(') while generating texture \""
1015 << name << "\"" << std::endl;
1027 if (paren_bal > 0) {
1028 errorstream << "generateImage(): unbalanced parentheses"
1029 << "(missing matching '(') while generating texture \""
1030 << name << "\"" << std::endl;
1035 video::IImage *baseimg = NULL;
1038 If separator was found, make the base image
1039 using a recursive call.
1041 if (last_separator_pos != -1) {
1042 baseimg = generateImage(name.substr(0, last_separator_pos));
1046 video::IVideoDriver* driver = m_device->getVideoDriver();
1047 sanity_check(driver);
1050 Parse out the last part of the name of the image and act
1054 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1057 If this name is enclosed in parentheses, generate it
1058 and blit it onto the base image
1060 if (last_part_of_name[0] == paren_open
1061 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1062 std::string name2 = last_part_of_name.substr(1,
1063 last_part_of_name.size() - 2);
1064 video::IImage *tmp = generateImage(name2);
1066 errorstream << "generateImage(): "
1067 "Failed to generate \"" << name2 << "\""
1071 core::dimension2d<u32> dim = tmp->getDimension();
1073 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1074 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1076 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1077 // Generate image according to part of name
1078 errorstream << "generateImage(): "
1079 "Failed to generate \"" << last_part_of_name << "\""
1083 // If no resulting image, print a warning
1084 if (baseimg == NULL) {
1085 errorstream << "generateImage(): baseimg is NULL (attempted to"
1086 " create texture \"" << name << "\")" << std::endl;
1093 #include <GLES/gl.h>
1095 * Check and align image to npot2 if required by hardware
1096 * @param image image to check for npot2 alignment
1097 * @param driver driver to use for image operations
1098 * @return image or copy of image aligned to npot2
1100 video::IImage * Align2Npot2(video::IImage * image,
1101 video::IVideoDriver* driver)
1103 if (image == NULL) {
1107 core::dimension2d<u32> dim = image->getDimension();
1109 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1110 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1114 unsigned int height = npot2(dim.Height);
1115 unsigned int width = npot2(dim.Width);
1117 if ((dim.Height == height) &&
1118 (dim.Width == width)) {
1122 if (dim.Height > height) {
1126 if (dim.Width > width) {
1130 video::IImage *targetimage =
1131 driver->createImage(video::ECF_A8R8G8B8,
1132 core::dimension2d<u32>(width, height));
1134 if (targetimage != NULL) {
1135 image->copyToScaling(targetimage);
1143 bool TextureSource::generateImagePart(std::string part_of_name,
1144 video::IImage *& baseimg)
1146 video::IVideoDriver* driver = m_device->getVideoDriver();
1147 sanity_check(driver);
1149 // Stuff starting with [ are special commands
1150 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1152 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1154 image = Align2Npot2(image, driver);
1156 if (image == NULL) {
1157 if (part_of_name != "") {
1158 if (part_of_name.find("_normal.png") == std::string::npos){
1159 errorstream<<"generateImage(): Could not load image \""
1160 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1161 errorstream<<"generateImage(): Creating a dummy"
1162 <<" image for \""<<part_of_name<<"\""<<std::endl;
1164 infostream<<"generateImage(): Could not load normal map \""
1165 <<part_of_name<<"\""<<std::endl;
1166 infostream<<"generateImage(): Creating a dummy"
1167 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1171 // Just create a dummy image
1172 //core::dimension2d<u32> dim(2,2);
1173 core::dimension2d<u32> dim(1,1);
1174 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1175 sanity_check(image != NULL);
1176 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1177 image->setPixel(1,0, video::SColor(255,0,255,0));
1178 image->setPixel(0,1, video::SColor(255,0,0,255));
1179 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1180 image->setPixel(0,0, video::SColor(255,myrand()%256,
1181 myrand()%256,myrand()%256));
1182 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1183 myrand()%256,myrand()%256));
1184 image->setPixel(0,1, video::SColor(255,myrand()%256,
1185 myrand()%256,myrand()%256));
1186 image->setPixel(1,1, video::SColor(255,myrand()%256,
1187 myrand()%256,myrand()%256));*/
1190 // If base image is NULL, load as base.
1191 if (baseimg == NULL)
1193 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1195 Copy it this way to get an alpha channel.
1196 Otherwise images with alpha cannot be blitted on
1197 images that don't have alpha in the original file.
1199 core::dimension2d<u32> dim = image->getDimension();
1200 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1201 image->copyTo(baseimg);
1203 // Else blit on base.
1206 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1207 // Size of the copied area
1208 core::dimension2d<u32> dim = image->getDimension();
1209 //core::dimension2d<u32> dim(16,16);
1210 // Position to copy the blitted to in the base image
1211 core::position2d<s32> pos_to(0,0);
1212 // Position to copy the blitted from in the blitted image
1213 core::position2d<s32> pos_from(0,0);
1215 /*image->copyToWithAlpha(baseimg, pos_to,
1216 core::rect<s32>(pos_from, dim),
1217 video::SColor(255,255,255,255),
1219 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1226 // A special texture modification
1228 /*infostream<<"generateImage(): generating special "
1229 <<"modification \""<<part_of_name<<"\""
1235 Adds a cracking texture
1236 N = animation frame count, P = crack progression
1238 if (part_of_name.substr(0,6) == "[crack")
1240 if (baseimg == NULL) {
1241 errorstream<<"generateImagePart(): baseimg == NULL "
1242 <<"for part_of_name=\""<<part_of_name
1243 <<"\", cancelling."<<std::endl;
1247 // Crack image number and overlay option
1248 bool use_overlay = (part_of_name[6] == 'o');
1249 Strfnd sf(part_of_name);
1251 s32 frame_count = stoi(sf.next(":"));
1252 s32 progression = stoi(sf.next(":"));
1257 It is an image with a number of cracking stages
1260 video::IImage *img_crack = m_sourcecache.getOrLoad(
1261 "crack_anylength.png", m_device);
1263 if (img_crack && progression >= 0)
1265 draw_crack(img_crack, baseimg,
1266 use_overlay, frame_count,
1267 progression, driver);
1272 [combine:WxH:X,Y=filename:X,Y=filename2
1273 Creates a bigger texture from an amount of smaller ones
1275 else if (part_of_name.substr(0,8) == "[combine")
1277 Strfnd sf(part_of_name);
1279 u32 w0 = stoi(sf.next("x"));
1280 u32 h0 = stoi(sf.next(":"));
1281 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1282 core::dimension2d<u32> dim(w0,h0);
1283 if (baseimg == NULL) {
1284 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1285 baseimg->fill(video::SColor(0,0,0,0));
1287 while (sf.atend() == false) {
1288 u32 x = stoi(sf.next(","));
1289 u32 y = stoi(sf.next("="));
1290 std::string filename = sf.next(":");
1291 infostream<<"Adding \""<<filename
1292 <<"\" to combined ("<<x<<","<<y<<")"
1294 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1296 core::dimension2d<u32> dim = img->getDimension();
1297 infostream<<"Size "<<dim.Width
1298 <<"x"<<dim.Height<<std::endl;
1299 core::position2d<s32> pos_base(x, y);
1300 video::IImage *img2 =
1301 driver->createImage(video::ECF_A8R8G8B8, dim);
1304 /*img2->copyToWithAlpha(baseimg, pos_base,
1305 core::rect<s32>(v2s32(0,0), dim),
1306 video::SColor(255,255,255,255),
1308 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1311 errorstream << "generateImagePart(): Failed to load image \""
1312 << filename << "\" for [combine" << std::endl;
1319 else if (part_of_name.substr(0,9) == "[brighten")
1321 if (baseimg == NULL) {
1322 errorstream<<"generateImagePart(): baseimg==NULL "
1323 <<"for part_of_name=\""<<part_of_name
1324 <<"\", cancelling."<<std::endl;
1332 Make image completely opaque.
1333 Used for the leaves texture when in old leaves mode, so
1334 that the transparent parts don't look completely black
1335 when simple alpha channel is used for rendering.
1337 else if (part_of_name.substr(0,8) == "[noalpha")
1339 if (baseimg == NULL){
1340 errorstream<<"generateImagePart(): baseimg==NULL "
1341 <<"for part_of_name=\""<<part_of_name
1342 <<"\", cancelling."<<std::endl;
1346 core::dimension2d<u32> dim = baseimg->getDimension();
1348 // Set alpha to full
1349 for (u32 y=0; y<dim.Height; y++)
1350 for (u32 x=0; x<dim.Width; x++)
1352 video::SColor c = baseimg->getPixel(x,y);
1354 baseimg->setPixel(x,y,c);
1359 Convert one color to transparent.
1361 else if (part_of_name.substr(0,11) == "[makealpha:")
1363 if (baseimg == NULL) {
1364 errorstream<<"generateImagePart(): baseimg == NULL "
1365 <<"for part_of_name=\""<<part_of_name
1366 <<"\", cancelling."<<std::endl;
1370 Strfnd sf(part_of_name.substr(11));
1371 u32 r1 = stoi(sf.next(","));
1372 u32 g1 = stoi(sf.next(","));
1373 u32 b1 = stoi(sf.next(""));
1374 std::string filename = sf.next("");
1376 core::dimension2d<u32> dim = baseimg->getDimension();
1378 /*video::IImage *oldbaseimg = baseimg;
1379 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1380 oldbaseimg->copyTo(baseimg);
1381 oldbaseimg->drop();*/
1383 // Set alpha to full
1384 for (u32 y=0; y<dim.Height; y++)
1385 for (u32 x=0; x<dim.Width; x++)
1387 video::SColor c = baseimg->getPixel(x,y);
1389 u32 g = c.getGreen();
1390 u32 b = c.getBlue();
1391 if (!(r == r1 && g == g1 && b == b1))
1394 baseimg->setPixel(x,y,c);
1399 Rotates and/or flips the image.
1401 N can be a number (between 0 and 7) or a transform name.
1402 Rotations are counter-clockwise.
1404 1 R90 rotate by 90 degrees
1405 2 R180 rotate by 180 degrees
1406 3 R270 rotate by 270 degrees
1408 5 FXR90 flip X then rotate by 90 degrees
1410 7 FYR90 flip Y then rotate by 90 degrees
1412 Note: Transform names can be concatenated to produce
1413 their product (applies the first then the second).
1414 The resulting transform will be equivalent to one of the
1415 eight existing ones, though (see: dihedral group).
1417 else if (part_of_name.substr(0,10) == "[transform")
1419 if (baseimg == NULL) {
1420 errorstream<<"generateImagePart(): baseimg == NULL "
1421 <<"for part_of_name=\""<<part_of_name
1422 <<"\", cancelling."<<std::endl;
1426 u32 transform = parseImageTransform(part_of_name.substr(10));
1427 core::dimension2d<u32> dim = imageTransformDimension(
1428 transform, baseimg->getDimension());
1429 video::IImage *image = driver->createImage(
1430 baseimg->getColorFormat(), dim);
1431 sanity_check(image != NULL);
1432 imageTransform(transform, baseimg, image);
1437 [inventorycube{topimage{leftimage{rightimage
1438 In every subimage, replace ^ with &.
1439 Create an "inventory cube".
1440 NOTE: This should be used only on its own.
1441 Example (a grass block (not actually used in game):
1442 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1444 else if (part_of_name.substr(0,14) == "[inventorycube")
1446 if (baseimg != NULL){
1447 errorstream<<"generateImagePart(): baseimg != NULL "
1448 <<"for part_of_name=\""<<part_of_name
1449 <<"\", cancelling."<<std::endl;
1453 str_replace(part_of_name, '&', '^');
1454 Strfnd sf(part_of_name);
1456 std::string imagename_top = sf.next("{");
1457 std::string imagename_left = sf.next("{");
1458 std::string imagename_right = sf.next("{");
1460 // Generate images for the faces of the cube
1461 video::IImage *img_top = generateImage(imagename_top);
1462 video::IImage *img_left = generateImage(imagename_left);
1463 video::IImage *img_right = generateImage(imagename_right);
1465 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1466 errorstream << "generateImagePart(): Failed to create textures"
1467 << " for inventorycube \"" << part_of_name << "\""
1469 baseimg = generateImage(imagename_top);
1474 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1475 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1477 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1478 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1480 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1481 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1484 // Create textures from images
1485 video::ITexture *texture_top = driver->addTexture(
1486 (imagename_top + "__temp__").c_str(), img_top);
1487 video::ITexture *texture_left = driver->addTexture(
1488 (imagename_left + "__temp__").c_str(), img_left);
1489 video::ITexture *texture_right = driver->addTexture(
1490 (imagename_right + "__temp__").c_str(), img_right);
1491 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1499 Draw a cube mesh into a render target texture
1501 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1502 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1503 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1504 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1505 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1506 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1507 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1508 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1510 TextureFromMeshParams params;
1512 params.dim.set(64, 64);
1513 params.rtt_texture_name = part_of_name + "_RTT";
1514 // We will delete the rtt texture ourselves
1515 params.delete_texture_on_shutdown = false;
1516 params.camera_position.set(0, 1.0, -1.5);
1517 params.camera_position.rotateXZBy(45);
1518 params.camera_lookat.set(0, 0, 0);
1519 // Set orthogonal projection
1520 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1521 1.65, 1.65, 0, 100);
1523 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1524 params.light_position.set(10, 100, -50);
1525 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1526 params.light_radius = 1000;
1528 video::ITexture *rtt = generateTextureFromMesh(params);
1534 driver->removeTexture(texture_top);
1535 driver->removeTexture(texture_left);
1536 driver->removeTexture(texture_right);
1539 baseimg = generateImage(imagename_top);
1543 // Create image of render target
1544 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1545 FATAL_ERROR_IF(!image, "Could not create image of render target");
1548 driver->removeTexture(rtt);
1550 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1553 image->copyTo(baseimg);
1558 [lowpart:percent:filename
1559 Adds the lower part of a texture
1561 else if (part_of_name.substr(0,9) == "[lowpart:")
1563 Strfnd sf(part_of_name);
1565 u32 percent = stoi(sf.next(":"));
1566 std::string filename = sf.next(":");
1567 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1569 if (baseimg == NULL)
1570 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1571 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1574 core::dimension2d<u32> dim = img->getDimension();
1575 core::position2d<s32> pos_base(0, 0);
1576 video::IImage *img2 =
1577 driver->createImage(video::ECF_A8R8G8B8, dim);
1580 core::position2d<s32> clippos(0, 0);
1581 clippos.Y = dim.Height * (100-percent) / 100;
1582 core::dimension2d<u32> clipdim = dim;
1583 clipdim.Height = clipdim.Height * percent / 100 + 1;
1584 core::rect<s32> cliprect(clippos, clipdim);
1585 img2->copyToWithAlpha(baseimg, pos_base,
1586 core::rect<s32>(v2s32(0,0), dim),
1587 video::SColor(255,255,255,255),
1594 Crops a frame of a vertical animation.
1595 N = frame count, I = frame index
1597 else if (part_of_name.substr(0,15) == "[verticalframe:")
1599 Strfnd sf(part_of_name);
1601 u32 frame_count = stoi(sf.next(":"));
1602 u32 frame_index = stoi(sf.next(":"));
1604 if (baseimg == NULL){
1605 errorstream<<"generateImagePart(): baseimg != NULL "
1606 <<"for part_of_name=\""<<part_of_name
1607 <<"\", cancelling."<<std::endl;
1611 v2u32 frame_size = baseimg->getDimension();
1612 frame_size.Y /= frame_count;
1614 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1617 errorstream<<"generateImagePart(): Could not create image "
1618 <<"for part_of_name=\""<<part_of_name
1619 <<"\", cancelling."<<std::endl;
1623 // Fill target image with transparency
1624 img->fill(video::SColor(0,0,0,0));
1626 core::dimension2d<u32> dim = frame_size;
1627 core::position2d<s32> pos_dst(0, 0);
1628 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1629 baseimg->copyToWithAlpha(img, pos_dst,
1630 core::rect<s32>(pos_src, dim),
1631 video::SColor(255,255,255,255),
1639 Applies a mask to an image
1641 else if (part_of_name.substr(0,6) == "[mask:")
1643 if (baseimg == NULL) {
1644 errorstream << "generateImage(): baseimg == NULL "
1645 << "for part_of_name=\"" << part_of_name
1646 << "\", cancelling." << std::endl;
1649 Strfnd sf(part_of_name);
1651 std::string filename = sf.next(":");
1653 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1655 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1656 img->getDimension());
1658 errorstream << "generateImage(): Failed to load \""
1659 << filename << "\".";
1664 Overlays image with given color
1665 color = color as ColorString
1667 else if (part_of_name.substr(0,10) == "[colorize:") {
1668 Strfnd sf(part_of_name);
1670 std::string color_str = sf.next(":");
1671 std::string ratio_str = sf.next(":");
1673 if (baseimg == NULL) {
1674 errorstream << "generateImagePart(): baseimg != NULL "
1675 << "for part_of_name=\"" << part_of_name
1676 << "\", cancelling." << std::endl;
1680 video::SColor color;
1683 if (!parseColorString(color_str, color, false))
1686 if (is_number(ratio_str))
1687 ratio = mystoi(ratio_str, 0, 255);
1689 core::dimension2d<u32> dim = baseimg->getDimension();
1690 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1693 errorstream << "generateImagePart(): Could not create image "
1694 << "for part_of_name=\"" << part_of_name
1695 << "\", cancelling." << std::endl;
1699 img->fill(video::SColor(color));
1700 // Overlay the colored image
1701 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1704 else if (part_of_name.substr(0,19) == "[autoupscaleformesh") {
1705 /* Upscale textures to user's requested minimum size. This is a trick to make
1706 * filters look as good on low-res textures as on high-res ones, by making
1707 * low-res textures BECOME high-res ones. This is helpful for worlds that
1708 * mix high- and low-res textures, or for mods with least-common-denominator
1709 * textures that don't have the resources to offer high-res alternatives.
1711 s32 scaleto = g_settings->getS32("texture_min_size");
1713 const core::dimension2d<u32> dim = baseimg->getDimension();
1715 /* Calculate scaling needed to make the shortest texture dimension
1716 * equal to the target minimum. If e.g. this is a vertical frames
1717 * animation, the short dimension will be the real size.
1719 u32 xscale = scaleto / dim.Width;
1720 u32 yscale = scaleto / dim.Height;
1721 u32 scale = (xscale > yscale) ? xscale : yscale;
1723 // Never downscale; only scale up by 2x or more.
1725 u32 w = scale * dim.Width;
1726 u32 h = scale * dim.Height;
1727 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1728 video::IImage *newimg = driver->createImage(
1729 baseimg->getColorFormat(), newdim);
1730 baseimg->copyToScaling(newimg);
1738 errorstream << "generateImagePart(): Invalid "
1739 " modification: \"" << part_of_name << "\"" << std::endl;
1747 Draw an image on top of an another one, using the alpha channel of the
1750 This exists because IImage::copyToWithAlpha() doesn't seem to always
1753 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1754 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1756 for (u32 y0=0; y0<size.Y; y0++)
1757 for (u32 x0=0; x0<size.X; x0++)
1759 s32 src_x = src_pos.X + x0;
1760 s32 src_y = src_pos.Y + y0;
1761 s32 dst_x = dst_pos.X + x0;
1762 s32 dst_y = dst_pos.Y + y0;
1763 video::SColor src_c = src->getPixel(src_x, src_y);
1764 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1765 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1766 dst->setPixel(dst_x, dst_y, dst_c);
1771 Draw an image on top of an another one, using the alpha channel of the
1772 source image; only modify fully opaque pixels in destinaion
1774 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1775 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1777 for (u32 y0=0; y0<size.Y; y0++)
1778 for (u32 x0=0; x0<size.X; x0++)
1780 s32 src_x = src_pos.X + x0;
1781 s32 src_y = src_pos.Y + y0;
1782 s32 dst_x = dst_pos.X + x0;
1783 s32 dst_y = dst_pos.Y + y0;
1784 video::SColor src_c = src->getPixel(src_x, src_y);
1785 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1786 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1788 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1789 dst->setPixel(dst_x, dst_y, dst_c);
1795 Draw an image on top of an another one, using the specified ratio
1796 modify all partially-opaque pixels in the destination.
1798 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1799 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1801 for (u32 y0 = 0; y0 < size.Y; y0++)
1802 for (u32 x0 = 0; x0 < size.X; x0++)
1804 s32 src_x = src_pos.X + x0;
1805 s32 src_y = src_pos.Y + y0;
1806 s32 dst_x = dst_pos.X + x0;
1807 s32 dst_y = dst_pos.Y + y0;
1808 video::SColor src_c = src->getPixel(src_x, src_y);
1809 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1810 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1813 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1815 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1816 dst->setPixel(dst_x, dst_y, dst_c);
1822 Apply mask to destination
1824 static void apply_mask(video::IImage *mask, video::IImage *dst,
1825 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1827 for (u32 y0 = 0; y0 < size.Y; y0++) {
1828 for (u32 x0 = 0; x0 < size.X; x0++) {
1829 s32 mask_x = x0 + mask_pos.X;
1830 s32 mask_y = y0 + mask_pos.Y;
1831 s32 dst_x = x0 + dst_pos.X;
1832 s32 dst_y = y0 + dst_pos.Y;
1833 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1834 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1835 dst_c.color &= mask_c.color;
1836 dst->setPixel(dst_x, dst_y, dst_c);
1841 static void draw_crack(video::IImage *crack, video::IImage *dst,
1842 bool use_overlay, s32 frame_count, s32 progression,
1843 video::IVideoDriver *driver)
1845 // Dimension of destination image
1846 core::dimension2d<u32> dim_dst = dst->getDimension();
1847 // Dimension of original image
1848 core::dimension2d<u32> dim_crack = crack->getDimension();
1849 // Count of crack stages
1850 s32 crack_count = dim_crack.Height / dim_crack.Width;
1851 // Limit frame_count
1852 if (frame_count > (s32) dim_dst.Height)
1853 frame_count = dim_dst.Height;
1854 if (frame_count < 1)
1856 // Limit progression
1857 if (progression > crack_count-1)
1858 progression = crack_count-1;
1859 // Dimension of a single crack stage
1860 core::dimension2d<u32> dim_crack_cropped(
1864 // Dimension of the scaled crack stage,
1865 // which is the same as the dimension of a single destination frame
1866 core::dimension2d<u32> dim_crack_scaled(
1868 dim_dst.Height / frame_count
1870 // Create cropped and scaled crack images
1871 video::IImage *crack_cropped = driver->createImage(
1872 video::ECF_A8R8G8B8, dim_crack_cropped);
1873 video::IImage *crack_scaled = driver->createImage(
1874 video::ECF_A8R8G8B8, dim_crack_scaled);
1876 if (crack_cropped && crack_scaled)
1879 v2s32 pos_crack(0, progression*dim_crack.Width);
1880 crack->copyTo(crack_cropped,
1882 core::rect<s32>(pos_crack, dim_crack_cropped));
1883 // Scale crack image by copying
1884 crack_cropped->copyToScaling(crack_scaled);
1885 // Copy or overlay crack image onto each frame
1886 for (s32 i = 0; i < frame_count; ++i)
1888 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1891 blit_with_alpha_overlay(crack_scaled, dst,
1892 v2s32(0,0), dst_pos,
1897 blit_with_alpha(crack_scaled, dst,
1898 v2s32(0,0), dst_pos,
1905 crack_scaled->drop();
1908 crack_cropped->drop();
1911 void brighten(video::IImage *image)
1916 core::dimension2d<u32> dim = image->getDimension();
1918 for (u32 y=0; y<dim.Height; y++)
1919 for (u32 x=0; x<dim.Width; x++)
1921 video::SColor c = image->getPixel(x,y);
1922 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1923 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1924 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1925 image->setPixel(x,y,c);
1929 u32 parseImageTransform(const std::string& s)
1931 int total_transform = 0;
1933 std::string transform_names[8];
1934 transform_names[0] = "i";
1935 transform_names[1] = "r90";
1936 transform_names[2] = "r180";
1937 transform_names[3] = "r270";
1938 transform_names[4] = "fx";
1939 transform_names[6] = "fy";
1941 std::size_t pos = 0;
1942 while(pos < s.size())
1945 for (int i = 0; i <= 7; ++i)
1947 const std::string &name_i = transform_names[i];
1949 if (s[pos] == ('0' + i))
1955 else if (!(name_i.empty()) &&
1956 lowercase(s.substr(pos, name_i.size())) == name_i)
1959 pos += name_i.size();
1966 // Multiply total_transform and transform in the group D4
1969 new_total = (transform + total_transform) % 4;
1971 new_total = (transform - total_transform + 8) % 4;
1972 if ((transform >= 4) ^ (total_transform >= 4))
1975 total_transform = new_total;
1977 return total_transform;
1980 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1982 if (transform % 2 == 0)
1985 return core::dimension2d<u32>(dim.Height, dim.Width);
1988 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1990 if (src == NULL || dst == NULL)
1993 core::dimension2d<u32> dstdim = dst->getDimension();
1996 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1997 assert(transform <= 7);
2000 Compute the transformation from source coordinates (sx,sy)
2001 to destination coordinates (dx,dy).
2005 if (transform == 0) // identity
2006 sxn = 0, syn = 2; // sx = dx, sy = dy
2007 else if (transform == 1) // rotate by 90 degrees ccw
2008 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2009 else if (transform == 2) // rotate by 180 degrees
2010 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2011 else if (transform == 3) // rotate by 270 degrees ccw
2012 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2013 else if (transform == 4) // flip x
2014 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2015 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2016 sxn = 2, syn = 0; // sx = dy, sy = dx
2017 else if (transform == 6) // flip y
2018 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2019 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2020 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2022 for (u32 dy=0; dy<dstdim.Height; dy++)
2023 for (u32 dx=0; dx<dstdim.Width; dx++)
2025 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2026 u32 sx = entries[sxn];
2027 u32 sy = entries[syn];
2028 video::SColor c = src->getPixel(sx,sy);
2029 dst->setPixel(dx,dy,c);
2033 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2036 if (isKnownSourceImage("override_normal.png"))
2037 return getTexture("override_normal.png", &id);
2038 std::string fname_base = name;
2039 std::string normal_ext = "_normal.png";
2040 size_t pos = fname_base.find(".");
2041 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2042 if (isKnownSourceImage(fname_normal)) {
2043 // look for image extension and replace it
2045 while ((i = fname_base.find(".", i)) != std::string::npos) {
2046 fname_base.replace(i, 4, normal_ext);
2047 i += normal_ext.length();
2049 return getTexture(fname_base, &id);