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.
21 #include "irrlichttypes_extrabloated.h"
23 #include "main.h" // for g_settings
27 #include <ICameraSceneNode.h>
30 #include "util/string.h"
31 #include "util/container.h"
32 #include "util/thread.h"
33 #include "util/numeric.h"
40 A cache from texture name to texture path
42 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
45 Replaces the filename extension.
47 std::string image = "a/image.png"
48 replace_ext(image, "jpg")
49 -> image = "a/image.jpg"
50 Returns true on success.
52 static bool replace_ext(std::string &path, const char *ext)
56 // Find place of last dot, fail if \ or / found.
58 for(s32 i=path.size()-1; i>=0; i--)
66 if(path[i] == '\\' || path[i] == '/')
69 // If not found, return an empty string
72 // Else make the new path
73 path = path.substr(0, last_dot_i+1) + ext;
78 Find out the full path of an image by trying different filename
83 std::string getImagePath(std::string path)
85 // A NULL-ended list of possible image extensions
86 const char *extensions[] = {
87 "png", "jpg", "bmp", "tga",
88 "pcx", "ppm", "psd", "wal", "rgb",
91 // If there is no extension, add one
92 if(removeStringEnd(path, extensions) == "")
94 // Check paths until something is found to exist
95 const char **ext = extensions;
97 bool r = replace_ext(path, *ext);
100 if(fs::PathExists(path))
103 while((++ext) != NULL);
109 Gets the path to a texture by first checking if the texture exists
110 in texture_path and if not, using the data path.
112 Checks all supported extensions by replacing the original extension.
114 If not found, returns "".
116 Utilizes a thread-safe cache.
118 std::string getTexturePath(const std::string &filename)
120 std::string fullpath = "";
124 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
129 Check from texture_path
131 std::string texture_path = g_settings->get("texture_path");
132 if(texture_path != "")
134 std::string testpath = texture_path + DIR_DELIM + filename;
135 // Check all filename extensions. Returns "" if not found.
136 fullpath = getImagePath(testpath);
140 Check from default data directory
144 std::string base_path = porting::path_share + DIR_DELIM + "textures"
145 + DIR_DELIM + "base" + DIR_DELIM + "pack";
146 std::string testpath = base_path + DIR_DELIM + filename;
147 // Check all filename extensions. Returns "" if not found.
148 fullpath = getImagePath(testpath);
151 // Add to cache (also an empty result is cached)
152 g_texturename_to_path_cache.set(filename, fullpath);
158 void clearTextureNameCache()
160 g_texturename_to_path_cache.clear();
164 Stores internal information about a texture.
170 video::ITexture *texture;
173 const std::string &name_,
174 video::ITexture *texture_=NULL
183 SourceImageCache: A cache used for storing source images.
186 class SourceImageCache
189 ~SourceImageCache() {
190 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
191 iter != m_images.end(); iter++) {
192 iter->second->drop();
196 void insert(const std::string &name, video::IImage *img,
197 bool prefer_local, video::IVideoDriver *driver)
201 std::map<std::string, video::IImage*>::iterator n;
202 n = m_images.find(name);
203 if(n != m_images.end()){
208 video::IImage* toadd = img;
209 bool need_to_grab = true;
211 // Try to use local texture instead if asked to
213 std::string path = getTexturePath(name.c_str());
215 video::IImage *img2 = driver->createImageFromFile(path.c_str());
218 need_to_grab = false;
225 m_images[name] = toadd;
227 video::IImage* get(const std::string &name)
229 std::map<std::string, video::IImage*>::iterator n;
230 n = m_images.find(name);
231 if(n != m_images.end())
235 // Primarily fetches from cache, secondarily tries to read from filesystem
236 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
238 std::map<std::string, video::IImage*>::iterator n;
239 n = m_images.find(name);
240 if(n != m_images.end()){
241 n->second->grab(); // Grab for caller
244 video::IVideoDriver* driver = device->getVideoDriver();
245 std::string path = getTexturePath(name.c_str());
247 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
248 <<name<<"\""<<std::endl;
251 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
253 video::IImage *img = driver->createImageFromFile(path.c_str());
256 m_images[name] = img;
257 img->grab(); // Grab for caller
262 std::map<std::string, video::IImage*> m_images;
269 class TextureSource : public IWritableTextureSource
272 TextureSource(IrrlichtDevice *device);
273 virtual ~TextureSource();
277 Now, assume a texture with the id 1 exists, and has the name
278 "stone.png^mineral1".
279 Then a random thread calls getTextureId for a texture called
280 "stone.png^mineral1^crack0".
281 ...Now, WTF should happen? Well:
282 - getTextureId strips off stuff recursively from the end until
283 the remaining part is found, or nothing is left when
284 something is stripped out
286 But it is slow to search for textures by names and modify them
288 - ContentFeatures is made to contain ids for the basic plain
290 - Crack textures can be slow by themselves, but the framework
294 - Assume a texture with the id 1 exists, and has the name
295 "stone.png^mineral_coal.png".
296 - Now getNodeTile() stumbles upon a node which uses
297 texture id 1, and determines that MATERIAL_FLAG_CRACK
298 must be applied to the tile
299 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
300 has received the current crack level 0 from the client. It
301 finds out the name of the texture with getTextureName(1),
302 appends "^crack0" to it and gets a new texture id with
303 getTextureId("stone.png^mineral_coal.png^crack0").
308 Gets a texture id from cache or
309 - if main thread, from getTextureIdDirect
310 - if other thread, adds to request queue and waits for main thread
312 u32 getTextureId(const std::string &name);
318 "stone.png^mineral_coal.png"
319 "stone.png^mineral_coal.png^crack1"
321 - If texture specified by name is found from cache, return the
323 - Otherwise generate the texture, add to cache and return id.
324 Recursion is used to find out the largest found part of the
325 texture and continue based on it.
327 The id 0 points to a NULL texture. It is returned in case of error.
329 u32 getTextureIdDirect(const std::string &name);
331 // Finds out the name of a cached texture.
332 std::string getTextureName(u32 id);
335 If texture specified by the name pointed by the id doesn't
336 exist, create it, then return the cached texture.
338 Can be called from any thread. If called from some other thread
339 and not found in cache, the call is queued to the main thread
342 video::ITexture* getTexture(u32 id);
344 video::ITexture* getTexture(const std::string &name, u32 *id);
346 // Returns a pointer to the irrlicht device
347 virtual IrrlichtDevice* getDevice()
352 bool isKnownSourceImage(const std::string &name)
354 bool is_known = false;
355 bool cache_found = m_source_image_existence.get(name, &is_known);
358 // Not found in cache; find out if a local file exists
359 is_known = (getTexturePath(name) != "");
360 m_source_image_existence.set(name, is_known);
364 // Processes queued texture requests from other threads.
365 // Shall be called from the main thread.
368 // Insert an image into the cache without touching the filesystem.
369 // Shall be called from the main thread.
370 void insertSourceImage(const std::string &name, video::IImage *img);
372 // Rebuild images and textures from the current set of source images
373 // Shall be called from the main thread.
374 void rebuildImagesAndTextures();
376 // Render a mesh to a texture.
377 // Returns NULL if render-to-texture failed.
378 // Shall be called from the main thread.
379 video::ITexture* generateTextureFromMesh(
380 const TextureFromMeshParams ¶ms);
382 // Generates an image from a full string like
383 // "stone.png^mineral_coal.png^[crack:1:0".
384 // Shall be called from the main thread.
385 video::IImage* generateImageFromScratch(std::string name);
387 // Generate image based on a string like "stone.png" or "[crack:1:0".
388 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
389 // Shall be called from the main thread.
390 bool generateImage(std::string part_of_name, video::IImage *& baseimg);
392 video::ITexture* getNormalTexture(const std::string &name);
395 // The id of the thread that is allowed to use irrlicht directly
396 threadid_t m_main_thread;
397 // The irrlicht device
398 IrrlichtDevice *m_device;
400 // Cache of source images
401 // This should be only accessed from the main thread
402 SourceImageCache m_sourcecache;
404 // Thread-safe cache of what source images are known (true = known)
405 MutexedMap<std::string, bool> m_source_image_existence;
407 // A texture id is index in this array.
408 // The first position contains a NULL texture.
409 std::vector<TextureInfo> m_textureinfo_cache;
410 // Maps a texture name to an index in the former.
411 std::map<std::string, u32> m_name_to_id;
412 // The two former containers are behind this mutex
413 JMutex m_textureinfo_cache_mutex;
415 // Queued texture fetches (to be processed by the main thread)
416 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
418 // Textures that have been overwritten with other ones
419 // but can't be deleted because the ITexture* might still be used
420 std::list<video::ITexture*> m_texture_trash;
422 // Cached settings needed for making textures from meshes
423 bool m_setting_trilinear_filter;
424 bool m_setting_bilinear_filter;
425 bool m_setting_anisotropic_filter;
428 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
430 return new TextureSource(device);
433 TextureSource::TextureSource(IrrlichtDevice *device):
438 m_main_thread = get_current_thread_id();
440 // Add a NULL TextureInfo as the first index, named ""
441 m_textureinfo_cache.push_back(TextureInfo(""));
442 m_name_to_id[""] = 0;
444 // Cache some settings
445 // Note: Since this is only done once, the game must be restarted
446 // for these settings to take effect
447 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
448 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
449 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
452 TextureSource::~TextureSource()
454 video::IVideoDriver* driver = m_device->getVideoDriver();
456 unsigned int textures_before = driver->getTextureCount();
458 for (std::vector<TextureInfo>::iterator iter =
459 m_textureinfo_cache.begin();
460 iter != m_textureinfo_cache.end(); iter++)
464 driver->removeTexture(iter->texture);
466 m_textureinfo_cache.clear();
468 for (std::list<video::ITexture*>::iterator iter =
469 m_texture_trash.begin(); iter != m_texture_trash.end();
472 video::ITexture *t = *iter;
474 //cleanup trashed texture
475 driver->removeTexture(t);
478 infostream << "~TextureSource() "<< textures_before << "/"
479 << driver->getTextureCount() << std::endl;
482 u32 TextureSource::getTextureId(const std::string &name)
484 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
488 See if texture already exists
490 JMutexAutoLock lock(m_textureinfo_cache_mutex);
491 std::map<std::string, u32>::iterator n;
492 n = m_name_to_id.find(name);
493 if(n != m_name_to_id.end())
502 if(get_current_thread_id() == m_main_thread)
504 return getTextureIdDirect(name);
508 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
510 // We're gonna ask the result to be put into here
511 static ResultQueue<std::string, u32, u8, u8> result_queue;
513 // Throw a request in
514 m_get_texture_queue.add(name, 0, 0, &result_queue);
516 /*infostream<<"Waiting for texture from main thread, name=\""
517 <<name<<"\""<<std::endl;*/
522 // Wait result for a second
523 GetResult<std::string, u32, u8, u8>
524 result = result_queue.pop_front(1000);
526 if (result.key == name) {
531 catch(ItemNotFoundException &e)
533 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
538 infostream<<"getTextureId(): Failed"<<std::endl;
543 // Draw an image on top of an another one, using the alpha channel of the
545 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
546 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
548 // Like blit_with_alpha, but only modifies destination pixels that
550 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
551 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
553 // Draw or overlay a crack
554 static void draw_crack(video::IImage *crack, video::IImage *dst,
555 bool use_overlay, s32 frame_count, s32 progression,
556 video::IVideoDriver *driver);
559 void brighten(video::IImage *image);
560 // Parse a transform name
561 u32 parseImageTransform(const std::string& s);
562 // Apply transform to image dimension
563 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
564 // Apply transform to image data
565 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
568 This method generates all the textures
570 u32 TextureSource::getTextureIdDirect(const std::string &name)
572 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
574 // Empty name means texture 0
577 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
582 Calling only allowed from main thread
584 if(get_current_thread_id() != m_main_thread)
586 errorstream<<"TextureSource::getTextureIdDirect() "
587 "called not from main thread"<<std::endl;
592 See if texture already exists
595 JMutexAutoLock lock(m_textureinfo_cache_mutex);
597 std::map<std::string, u32>::iterator n;
598 n = m_name_to_id.find(name);
599 if(n != m_name_to_id.end())
601 /*infostream<<"getTextureIdDirect(): \""<<name
602 <<"\" found in cache"<<std::endl;*/
607 /*infostream<<"getTextureIdDirect(): \""<<name
608 <<"\" NOT found in cache. Creating it."<<std::endl;*/
614 char separator = '^';
617 This is set to the id of the base image.
618 If left 0, there is no base image and a completely new image
621 u32 base_image_id = 0;
623 // Find last meta separator in name
624 s32 last_separator_position = -1;
625 for(s32 i=name.size()-1; i>=0; i--)
627 if(name[i] == separator)
629 last_separator_position = i;
634 If separator was found, construct the base name and make the
635 base image using a recursive call
637 std::string base_image_name;
638 if(last_separator_position != -1)
640 // Construct base name
641 base_image_name = name.substr(0, last_separator_position);
642 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
643 " to get base image of \""<<name<<"\" = \""
644 <<base_image_name<<"\""<<std::endl;*/
645 base_image_id = getTextureIdDirect(base_image_name);
648 //infostream<<"base_image_id="<<base_image_id<<std::endl;
650 video::IVideoDriver* driver = m_device->getVideoDriver();
653 video::ITexture *t = NULL;
656 An image will be built from files and then converted into a texture.
658 video::IImage *baseimg = NULL;
660 // If a base image was found, copy it to baseimg
661 if(base_image_id != 0)
663 JMutexAutoLock lock(m_textureinfo_cache_mutex);
665 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
667 if(ti->texture == NULL)
669 infostream<<"getTextureIdDirect(): WARNING: NULL Texture in "
670 <<"cache: \""<<base_image_name<<"\""
675 core::dimension2d<u32> dim = ti->texture->getSize();
677 baseimg = driver->createImage(ti->texture,v2s32(0,0), dim);
679 /*infostream<<"getTextureIdDirect(): Loaded \""
680 <<base_image_name<<"\" from image cache"
686 Parse out the last part of the name of the image and act
690 std::string last_part_of_name = name.substr(last_separator_position+1);
691 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
693 // Generate image according to part of name
694 if(!generateImage(last_part_of_name, baseimg))
696 errorstream<<"getTextureIdDirect(): "
697 "failed to generate \""<<last_part_of_name<<"\""
701 // If no resulting image, print a warning
704 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
705 " create texture \""<<name<<"\""<<std::endl;
711 baseimg = Align2Npot2(baseimg, driver);
713 // Create texture from resulting image
714 t = driver->addTexture(name.c_str(), baseimg);
719 Add texture to caches (add NULL textures too)
722 JMutexAutoLock lock(m_textureinfo_cache_mutex);
724 u32 id = m_textureinfo_cache.size();
725 TextureInfo ti(name, t);
726 m_textureinfo_cache.push_back(ti);
727 m_name_to_id[name] = id;
732 std::string TextureSource::getTextureName(u32 id)
734 JMutexAutoLock lock(m_textureinfo_cache_mutex);
736 if(id >= m_textureinfo_cache.size())
738 errorstream<<"TextureSource::getTextureName(): id="<<id
739 <<" >= m_textureinfo_cache.size()="
740 <<m_textureinfo_cache.size()<<std::endl;
744 return m_textureinfo_cache[id].name;
747 video::ITexture* TextureSource::getTexture(u32 id)
749 JMutexAutoLock lock(m_textureinfo_cache_mutex);
751 if(id >= m_textureinfo_cache.size())
754 return m_textureinfo_cache[id].texture;
757 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
759 u32 actual_id = getTextureId(name);
763 return getTexture(actual_id);
766 void TextureSource::processQueue()
771 //NOTE this is only thread safe for ONE consumer thread!
772 if(!m_get_texture_queue.empty())
774 GetRequest<std::string, u32, u8, u8>
775 request = m_get_texture_queue.pop();
777 /*infostream<<"TextureSource::processQueue(): "
778 <<"got texture request with "
779 <<"name=\""<<request.key<<"\""
782 m_get_texture_queue.pushResult(request,getTextureIdDirect(request.key));
786 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
788 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
790 assert(get_current_thread_id() == m_main_thread);
792 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
793 m_source_image_existence.set(name, true);
796 void TextureSource::rebuildImagesAndTextures()
798 JMutexAutoLock lock(m_textureinfo_cache_mutex);
800 video::IVideoDriver* driver = m_device->getVideoDriver();
804 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
805 TextureInfo *ti = &m_textureinfo_cache[i];
806 video::IImage *img = generateImageFromScratch(ti->name);
808 img = Align2Npot2(img,driver);
809 assert(img->getDimension().Height == npot2(img->getDimension().Height));
810 assert(img->getDimension().Width == npot2(img->getDimension().Width));
812 // Create texture from resulting image
813 video::ITexture *t = NULL;
815 t = driver->addTexture(ti->name.c_str(), img);
818 video::ITexture *t_old = ti->texture;
823 m_texture_trash.push_back(t_old);
827 video::ITexture* TextureSource::generateTextureFromMesh(
828 const TextureFromMeshParams ¶ms)
830 video::IVideoDriver *driver = m_device->getVideoDriver();
834 const GLubyte* renderstr = glGetString(GL_RENDERER);
835 std::string renderer((char*) renderstr);
837 // use no render to texture hack
839 (renderer.find("Adreno") != std::string::npos) ||
840 (renderer.find("Mali") != std::string::npos) ||
841 (renderer.find("Immersion") != std::string::npos) ||
842 (renderer.find("Tegra") != std::string::npos) ||
843 g_settings->getBool("inventory_image_hack")
845 // Get a scene manager
846 scene::ISceneManager *smgr_main = m_device->getSceneManager();
848 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
851 const float scaling = 0.2;
853 scene::IMeshSceneNode* meshnode =
854 smgr->addMeshSceneNode(params.mesh, NULL,
855 -1, v3f(0,0,0), v3f(0,0,0),
856 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
857 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
858 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
859 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
860 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
861 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
863 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
864 params.camera_position, params.camera_lookat);
865 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
866 camera->setProjectionMatrix(params.camera_projection_matrix, false);
868 smgr->setAmbientLight(params.ambient_light);
869 smgr->addLightSceneNode(0,
870 params.light_position,
872 params.light_radius*scaling);
874 core::dimension2d<u32> screen = driver->getScreenSize();
877 driver->beginScene(true, true, video::SColor(0,0,0,0));
878 driver->clearZBuffer();
881 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
883 irr::video::IImage* rawImage =
884 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
886 u8* pixels = static_cast<u8*>(rawImage->lock());
893 core::rect<s32> source(
894 screen.Width /2 - (screen.Width * (scaling / 2)),
895 screen.Height/2 - (screen.Height * (scaling / 2)),
896 screen.Width /2 + (screen.Width * (scaling / 2)),
897 screen.Height/2 + (screen.Height * (scaling / 2))
900 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
901 partsize.Width, partsize.Height, GL_RGBA,
902 GL_UNSIGNED_BYTE, pixels);
906 // Drop scene manager
909 unsigned int pixelcount = partsize.Width*partsize.Height;
912 for (unsigned int i=0; i < pixelcount; i++) {
930 video::IImage* inventory_image =
931 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
933 rawImage->copyToScaling(inventory_image);
936 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
937 inventory_image->drop();
940 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
944 driver->makeColorKeyTexture(rtt, v2s32(0,0));
946 if(params.delete_texture_on_shutdown)
947 m_texture_trash.push_back(rtt);
953 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
955 static bool warned = false;
958 errorstream<<"TextureSource::generateTextureFromMesh(): "
959 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
965 // Create render target texture
966 video::ITexture *rtt = driver->addRenderTargetTexture(
967 params.dim, params.rtt_texture_name.c_str(),
968 video::ECF_A8R8G8B8);
971 errorstream<<"TextureSource::generateTextureFromMesh(): "
972 <<"addRenderTargetTexture returned NULL."<<std::endl;
977 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
978 driver->removeTexture(rtt);
979 errorstream<<"TextureSource::generateTextureFromMesh(): "
980 <<"failed to set render target"<<std::endl;
984 // Get a scene manager
985 scene::ISceneManager *smgr_main = m_device->getSceneManager();
987 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
990 scene::IMeshSceneNode* meshnode =
991 smgr->addMeshSceneNode(params.mesh, NULL,
992 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
993 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
994 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
995 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
996 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
997 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
999 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1000 params.camera_position, params.camera_lookat);
1001 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
1002 camera->setProjectionMatrix(params.camera_projection_matrix, false);
1004 smgr->setAmbientLight(params.ambient_light);
1005 smgr->addLightSceneNode(0,
1006 params.light_position,
1008 params.light_radius);
1011 driver->beginScene(true, true, video::SColor(0,0,0,0));
1015 // Drop scene manager
1018 // Unset render target
1019 driver->setRenderTarget(0, false, true, 0);
1021 if(params.delete_texture_on_shutdown)
1022 m_texture_trash.push_back(rtt);
1027 video::IImage* TextureSource::generateImageFromScratch(std::string name)
1029 /*infostream<<"generateImageFromScratch(): "
1030 "\""<<name<<"\""<<std::endl;*/
1032 video::IVideoDriver *driver = m_device->getVideoDriver();
1039 video::IImage *baseimg = NULL;
1041 char separator = '^';
1043 // Find last meta separator in name
1044 s32 last_separator_position = name.find_last_of(separator);
1047 If separator was found, construct the base name and make the
1048 base image using a recursive call
1050 std::string base_image_name;
1051 if(last_separator_position != -1)
1053 // Construct base name
1054 base_image_name = name.substr(0, last_separator_position);
1055 baseimg = generateImageFromScratch(base_image_name);
1059 Parse out the last part of the name of the image and act
1063 std::string last_part_of_name = name.substr(last_separator_position+1);
1065 // Generate image according to part of name
1066 if(!generateImage(last_part_of_name, baseimg))
1068 errorstream<<"generateImageFromScratch(): "
1069 "failed to generate \""<<last_part_of_name<<"\""
1078 #include <GLES/gl.h>
1080 * Check and align image to npot2 if required by hardware
1081 * @param image image to check for npot2 alignment
1082 * @param driver driver to use for image operations
1083 * @return image or copy of image aligned to npot2
1085 video::IImage * Align2Npot2(video::IImage * image,
1086 video::IVideoDriver* driver)
1092 core::dimension2d<u32> dim = image->getDimension();
1094 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1095 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1099 unsigned int height = npot2(dim.Height);
1100 unsigned int width = npot2(dim.Width);
1102 if ((dim.Height == height) &&
1103 (dim.Width == width)) {
1107 if (dim.Height > height) {
1111 if (dim.Width > width) {
1115 video::IImage *targetimage =
1116 driver->createImage(video::ECF_A8R8G8B8,
1117 core::dimension2d<u32>(width, height));
1119 if (targetimage != NULL) {
1120 image->copyToScaling(targetimage);
1128 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
1130 video::IVideoDriver* driver = m_device->getVideoDriver();
1133 // Stuff starting with [ are special commands
1134 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1136 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1138 image = Align2Npot2(image,driver);
1140 if (image == NULL) {
1141 if (part_of_name != "") {
1142 if (part_of_name.find("_normal.png") == std::string::npos){
1143 errorstream<<"generateImage(): Could not load image \""
1144 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1145 errorstream<<"generateImage(): Creating a dummy"
1146 <<" image for \""<<part_of_name<<"\""<<std::endl;
1148 infostream<<"generateImage(): Could not load normal map \""
1149 <<part_of_name<<"\""<<std::endl;
1150 infostream<<"generateImage(): Creating a dummy"
1151 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1155 // Just create a dummy image
1156 //core::dimension2d<u32> dim(2,2);
1157 core::dimension2d<u32> dim(1,1);
1158 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1160 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1161 image->setPixel(1,0, video::SColor(255,0,255,0));
1162 image->setPixel(0,1, video::SColor(255,0,0,255));
1163 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1164 image->setPixel(0,0, video::SColor(255,myrand()%256,
1165 myrand()%256,myrand()%256));
1166 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1167 myrand()%256,myrand()%256));
1168 image->setPixel(0,1, video::SColor(255,myrand()%256,
1169 myrand()%256,myrand()%256));
1170 image->setPixel(1,1, video::SColor(255,myrand()%256,
1171 myrand()%256,myrand()%256));*/
1174 // If base image is NULL, load as base.
1177 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1179 Copy it this way to get an alpha channel.
1180 Otherwise images with alpha cannot be blitted on
1181 images that don't have alpha in the original file.
1183 core::dimension2d<u32> dim = image->getDimension();
1184 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1185 image->copyTo(baseimg);
1187 // Else blit on base.
1190 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1191 // Size of the copied area
1192 core::dimension2d<u32> dim = image->getDimension();
1193 //core::dimension2d<u32> dim(16,16);
1194 // Position to copy the blitted to in the base image
1195 core::position2d<s32> pos_to(0,0);
1196 // Position to copy the blitted from in the blitted image
1197 core::position2d<s32> pos_from(0,0);
1199 /*image->copyToWithAlpha(baseimg, pos_to,
1200 core::rect<s32>(pos_from, dim),
1201 video::SColor(255,255,255,255),
1203 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1210 // A special texture modification
1212 /*infostream<<"generateImage(): generating special "
1213 <<"modification \""<<part_of_name<<"\""
1219 Adds a cracking texture
1220 N = animation frame count, P = crack progression
1222 if(part_of_name.substr(0,6) == "[crack")
1226 errorstream<<"generateImage(): baseimg==NULL "
1227 <<"for part_of_name=\""<<part_of_name
1228 <<"\", cancelling."<<std::endl;
1232 // Crack image number and overlay option
1233 bool use_overlay = (part_of_name[6] == 'o');
1234 Strfnd sf(part_of_name);
1236 s32 frame_count = stoi(sf.next(":"));
1237 s32 progression = stoi(sf.next(":"));
1242 It is an image with a number of cracking stages
1245 video::IImage *img_crack = m_sourcecache.getOrLoad(
1246 "crack_anylength.png", m_device);
1248 if(img_crack && progression >= 0)
1250 draw_crack(img_crack, baseimg,
1251 use_overlay, frame_count,
1252 progression, driver);
1257 [combine:WxH:X,Y=filename:X,Y=filename2
1258 Creates a bigger texture from an amount of smaller ones
1260 else if(part_of_name.substr(0,8) == "[combine")
1262 Strfnd sf(part_of_name);
1264 u32 w0 = stoi(sf.next("x"));
1265 u32 h0 = stoi(sf.next(":"));
1266 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1267 core::dimension2d<u32> dim(w0,h0);
1270 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1271 baseimg->fill(video::SColor(0,0,0,0));
1273 while(sf.atend() == false)
1275 u32 x = stoi(sf.next(","));
1276 u32 y = stoi(sf.next("="));
1277 std::string filename = sf.next(":");
1278 infostream<<"Adding \""<<filename
1279 <<"\" to combined ("<<x<<","<<y<<")"
1281 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1284 core::dimension2d<u32> dim = img->getDimension();
1285 infostream<<"Size "<<dim.Width
1286 <<"x"<<dim.Height<<std::endl;
1287 core::position2d<s32> pos_base(x, y);
1288 video::IImage *img2 =
1289 driver->createImage(video::ECF_A8R8G8B8, dim);
1292 /*img2->copyToWithAlpha(baseimg, pos_base,
1293 core::rect<s32>(v2s32(0,0), dim),
1294 video::SColor(255,255,255,255),
1296 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1301 infostream<<"img==NULL"<<std::endl;
1308 else if(part_of_name.substr(0,9) == "[brighten")
1312 errorstream<<"generateImage(): baseimg==NULL "
1313 <<"for part_of_name=\""<<part_of_name
1314 <<"\", cancelling."<<std::endl;
1322 Make image completely opaque.
1323 Used for the leaves texture when in old leaves mode, so
1324 that the transparent parts don't look completely black
1325 when simple alpha channel is used for rendering.
1327 else if(part_of_name.substr(0,8) == "[noalpha")
1331 errorstream<<"generateImage(): baseimg==NULL "
1332 <<"for part_of_name=\""<<part_of_name
1333 <<"\", cancelling."<<std::endl;
1337 core::dimension2d<u32> dim = baseimg->getDimension();
1339 // Set alpha to full
1340 for(u32 y=0; y<dim.Height; y++)
1341 for(u32 x=0; x<dim.Width; x++)
1343 video::SColor c = baseimg->getPixel(x,y);
1345 baseimg->setPixel(x,y,c);
1350 Convert one color to transparent.
1352 else if(part_of_name.substr(0,11) == "[makealpha:")
1356 errorstream<<"generateImage(): baseimg==NULL "
1357 <<"for part_of_name=\""<<part_of_name
1358 <<"\", cancelling."<<std::endl;
1362 Strfnd sf(part_of_name.substr(11));
1363 u32 r1 = stoi(sf.next(","));
1364 u32 g1 = stoi(sf.next(","));
1365 u32 b1 = stoi(sf.next(""));
1366 std::string filename = sf.next("");
1368 core::dimension2d<u32> dim = baseimg->getDimension();
1370 /*video::IImage *oldbaseimg = baseimg;
1371 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1372 oldbaseimg->copyTo(baseimg);
1373 oldbaseimg->drop();*/
1375 // Set alpha to full
1376 for(u32 y=0; y<dim.Height; y++)
1377 for(u32 x=0; x<dim.Width; x++)
1379 video::SColor c = baseimg->getPixel(x,y);
1381 u32 g = c.getGreen();
1382 u32 b = c.getBlue();
1383 if(!(r == r1 && g == g1 && b == b1))
1386 baseimg->setPixel(x,y,c);
1391 Rotates and/or flips the image.
1393 N can be a number (between 0 and 7) or a transform name.
1394 Rotations are counter-clockwise.
1396 1 R90 rotate by 90 degrees
1397 2 R180 rotate by 180 degrees
1398 3 R270 rotate by 270 degrees
1400 5 FXR90 flip X then rotate by 90 degrees
1402 7 FYR90 flip Y then rotate by 90 degrees
1404 Note: Transform names can be concatenated to produce
1405 their product (applies the first then the second).
1406 The resulting transform will be equivalent to one of the
1407 eight existing ones, though (see: dihedral group).
1409 else if(part_of_name.substr(0,10) == "[transform")
1413 errorstream<<"generateImage(): baseimg==NULL "
1414 <<"for part_of_name=\""<<part_of_name
1415 <<"\", cancelling."<<std::endl;
1419 u32 transform = parseImageTransform(part_of_name.substr(10));
1420 core::dimension2d<u32> dim = imageTransformDimension(
1421 transform, baseimg->getDimension());
1422 video::IImage *image = driver->createImage(
1423 baseimg->getColorFormat(), dim);
1425 imageTransform(transform, baseimg, image);
1430 [inventorycube{topimage{leftimage{rightimage
1431 In every subimage, replace ^ with &.
1432 Create an "inventory cube".
1433 NOTE: This should be used only on its own.
1434 Example (a grass block (not actually used in game):
1435 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1437 else if(part_of_name.substr(0,14) == "[inventorycube")
1441 errorstream<<"generateImage(): baseimg!=NULL "
1442 <<"for part_of_name=\""<<part_of_name
1443 <<"\", cancelling."<<std::endl;
1447 str_replace_char(part_of_name, '&', '^');
1448 Strfnd sf(part_of_name);
1450 std::string imagename_top = sf.next("{");
1451 std::string imagename_left = sf.next("{");
1452 std::string imagename_right = sf.next("{");
1454 // Generate images for the faces of the cube
1455 video::IImage *img_top =
1456 generateImageFromScratch(imagename_top);
1457 video::IImage *img_left =
1458 generateImageFromScratch(imagename_left);
1459 video::IImage *img_right =
1460 generateImageFromScratch(imagename_right);
1461 assert(img_top && img_left && img_right);
1463 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1464 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1466 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1467 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1469 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1470 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1472 // Create textures from images
1473 video::ITexture *texture_top = driver->addTexture(
1474 (imagename_top + "__temp__").c_str(), img_top);
1475 video::ITexture *texture_left = driver->addTexture(
1476 (imagename_left + "__temp__").c_str(), img_left);
1477 video::ITexture *texture_right = driver->addTexture(
1478 (imagename_right + "__temp__").c_str(), img_right);
1479 assert(texture_top && texture_left && texture_right);
1487 Draw a cube mesh into a render target texture
1489 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1490 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1491 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1492 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1493 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1494 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1495 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1496 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1498 TextureFromMeshParams params;
1500 params.dim.set(64, 64);
1501 params.rtt_texture_name = part_of_name + "_RTT";
1502 // We will delete the rtt texture ourselves
1503 params.delete_texture_on_shutdown = false;
1504 params.camera_position.set(0, 1.0, -1.5);
1505 params.camera_position.rotateXZBy(45);
1506 params.camera_lookat.set(0, 0, 0);
1507 // Set orthogonal projection
1508 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1509 1.65, 1.65, 0, 100);
1511 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1512 params.light_position.set(10, 100, -50);
1513 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1514 params.light_radius = 1000;
1516 video::ITexture *rtt = generateTextureFromMesh(params);
1521 // Free textures of images
1522 driver->removeTexture(texture_top);
1523 driver->removeTexture(texture_left);
1524 driver->removeTexture(texture_right);
1528 baseimg = generateImageFromScratch(imagename_top);
1532 // Create image of render target
1533 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1537 driver->removeTexture(rtt);
1539 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1543 image->copyTo(baseimg);
1548 [lowpart:percent:filename
1549 Adds the lower part of a texture
1551 else if(part_of_name.substr(0,9) == "[lowpart:")
1553 Strfnd sf(part_of_name);
1555 u32 percent = stoi(sf.next(":"));
1556 std::string filename = sf.next(":");
1557 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1560 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1561 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1564 core::dimension2d<u32> dim = img->getDimension();
1565 core::position2d<s32> pos_base(0, 0);
1566 video::IImage *img2 =
1567 driver->createImage(video::ECF_A8R8G8B8, dim);
1570 core::position2d<s32> clippos(0, 0);
1571 clippos.Y = dim.Height * (100-percent) / 100;
1572 core::dimension2d<u32> clipdim = dim;
1573 clipdim.Height = clipdim.Height * percent / 100 + 1;
1574 core::rect<s32> cliprect(clippos, clipdim);
1575 img2->copyToWithAlpha(baseimg, pos_base,
1576 core::rect<s32>(v2s32(0,0), dim),
1577 video::SColor(255,255,255,255),
1584 Crops a frame of a vertical animation.
1585 N = frame count, I = frame index
1587 else if(part_of_name.substr(0,15) == "[verticalframe:")
1589 Strfnd sf(part_of_name);
1591 u32 frame_count = stoi(sf.next(":"));
1592 u32 frame_index = stoi(sf.next(":"));
1594 if(baseimg == NULL){
1595 errorstream<<"generateImage(): baseimg!=NULL "
1596 <<"for part_of_name=\""<<part_of_name
1597 <<"\", cancelling."<<std::endl;
1601 v2u32 frame_size = baseimg->getDimension();
1602 frame_size.Y /= frame_count;
1604 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1607 errorstream<<"generateImage(): Could not create image "
1608 <<"for part_of_name=\""<<part_of_name
1609 <<"\", cancelling."<<std::endl;
1613 // Fill target image with transparency
1614 img->fill(video::SColor(0,0,0,0));
1616 core::dimension2d<u32> dim = frame_size;
1617 core::position2d<s32> pos_dst(0, 0);
1618 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1619 baseimg->copyToWithAlpha(img, pos_dst,
1620 core::rect<s32>(pos_src, dim),
1621 video::SColor(255,255,255,255),
1629 errorstream<<"generateImage(): Invalid "
1630 " modification: \""<<part_of_name<<"\""<<std::endl;
1638 Draw an image on top of an another one, using the alpha channel of the
1641 This exists because IImage::copyToWithAlpha() doesn't seem to always
1644 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1645 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1647 for(u32 y0=0; y0<size.Y; y0++)
1648 for(u32 x0=0; x0<size.X; x0++)
1650 s32 src_x = src_pos.X + x0;
1651 s32 src_y = src_pos.Y + y0;
1652 s32 dst_x = dst_pos.X + x0;
1653 s32 dst_y = dst_pos.Y + y0;
1654 video::SColor src_c = src->getPixel(src_x, src_y);
1655 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1656 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1657 dst->setPixel(dst_x, dst_y, dst_c);
1662 Draw an image on top of an another one, using the alpha channel of the
1663 source image; only modify fully opaque pixels in destinaion
1665 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1666 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1668 for(u32 y0=0; y0<size.Y; y0++)
1669 for(u32 x0=0; x0<size.X; x0++)
1671 s32 src_x = src_pos.X + x0;
1672 s32 src_y = src_pos.Y + y0;
1673 s32 dst_x = dst_pos.X + x0;
1674 s32 dst_y = dst_pos.Y + y0;
1675 video::SColor src_c = src->getPixel(src_x, src_y);
1676 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1677 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1679 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1680 dst->setPixel(dst_x, dst_y, dst_c);
1685 static void draw_crack(video::IImage *crack, video::IImage *dst,
1686 bool use_overlay, s32 frame_count, s32 progression,
1687 video::IVideoDriver *driver)
1689 // Dimension of destination image
1690 core::dimension2d<u32> dim_dst = dst->getDimension();
1691 // Dimension of original image
1692 core::dimension2d<u32> dim_crack = crack->getDimension();
1693 // Count of crack stages
1694 s32 crack_count = dim_crack.Height / dim_crack.Width;
1695 // Limit frame_count
1696 if(frame_count > (s32) dim_dst.Height)
1697 frame_count = dim_dst.Height;
1700 // Limit progression
1701 if(progression > crack_count-1)
1702 progression = crack_count-1;
1703 // Dimension of a single crack stage
1704 core::dimension2d<u32> dim_crack_cropped(
1708 // Dimension of the scaled crack stage,
1709 // which is the same as the dimension of a single destination frame
1710 core::dimension2d<u32> dim_crack_scaled(
1712 dim_dst.Height / frame_count
1714 // Create cropped and scaled crack images
1715 video::IImage *crack_cropped = driver->createImage(
1716 video::ECF_A8R8G8B8, dim_crack_cropped);
1717 video::IImage *crack_scaled = driver->createImage(
1718 video::ECF_A8R8G8B8, dim_crack_scaled);
1720 if(crack_cropped && crack_scaled)
1723 v2s32 pos_crack(0, progression*dim_crack.Width);
1724 crack->copyTo(crack_cropped,
1726 core::rect<s32>(pos_crack, dim_crack_cropped));
1727 // Scale crack image by copying
1728 crack_cropped->copyToScaling(crack_scaled);
1729 // Copy or overlay crack image onto each frame
1730 for(s32 i = 0; i < frame_count; ++i)
1732 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1735 blit_with_alpha_overlay(crack_scaled, dst,
1736 v2s32(0,0), dst_pos,
1741 blit_with_alpha(crack_scaled, dst,
1742 v2s32(0,0), dst_pos,
1749 crack_scaled->drop();
1752 crack_cropped->drop();
1755 void brighten(video::IImage *image)
1760 core::dimension2d<u32> dim = image->getDimension();
1762 for(u32 y=0; y<dim.Height; y++)
1763 for(u32 x=0; x<dim.Width; x++)
1765 video::SColor c = image->getPixel(x,y);
1766 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1767 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1768 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1769 image->setPixel(x,y,c);
1773 u32 parseImageTransform(const std::string& s)
1775 int total_transform = 0;
1777 std::string transform_names[8];
1778 transform_names[0] = "i";
1779 transform_names[1] = "r90";
1780 transform_names[2] = "r180";
1781 transform_names[3] = "r270";
1782 transform_names[4] = "fx";
1783 transform_names[6] = "fy";
1785 std::size_t pos = 0;
1786 while(pos < s.size())
1789 for(int i = 0; i <= 7; ++i)
1791 const std::string &name_i = transform_names[i];
1793 if(s[pos] == ('0' + i))
1799 else if(!(name_i.empty()) &&
1800 lowercase(s.substr(pos, name_i.size())) == name_i)
1803 pos += name_i.size();
1810 // Multiply total_transform and transform in the group D4
1813 new_total = (transform + total_transform) % 4;
1815 new_total = (transform - total_transform + 8) % 4;
1816 if((transform >= 4) ^ (total_transform >= 4))
1819 total_transform = new_total;
1821 return total_transform;
1824 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1826 if(transform % 2 == 0)
1829 return core::dimension2d<u32>(dim.Height, dim.Width);
1832 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1834 if(src == NULL || dst == NULL)
1837 core::dimension2d<u32> srcdim = src->getDimension();
1838 core::dimension2d<u32> dstdim = dst->getDimension();
1840 assert(dstdim == imageTransformDimension(transform, srcdim));
1841 assert(transform >= 0 && transform <= 7);
1844 Compute the transformation from source coordinates (sx,sy)
1845 to destination coordinates (dx,dy).
1849 if(transform == 0) // identity
1850 sxn = 0, syn = 2; // sx = dx, sy = dy
1851 else if(transform == 1) // rotate by 90 degrees ccw
1852 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1853 else if(transform == 2) // rotate by 180 degrees
1854 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1855 else if(transform == 3) // rotate by 270 degrees ccw
1856 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1857 else if(transform == 4) // flip x
1858 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1859 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1860 sxn = 2, syn = 0; // sx = dy, sy = dx
1861 else if(transform == 6) // flip y
1862 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1863 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1864 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1866 for(u32 dy=0; dy<dstdim.Height; dy++)
1867 for(u32 dx=0; dx<dstdim.Width; dx++)
1869 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1870 u32 sx = entries[sxn];
1871 u32 sy = entries[syn];
1872 video::SColor c = src->getPixel(sx,sy);
1873 dst->setPixel(dx,dy,c);
1877 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1880 if (isKnownSourceImage("override_normal.png"))
1881 return getTexture("override_normal.png", &id);
1882 std::string fname_base = name;
1883 std::string normal_ext = "_normal.png";
1884 size_t pos = fname_base.find(".");
1885 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1886 if (isKnownSourceImage(fname_normal)) {
1887 // look for image extension and replace it
1889 while ((i = fname_base.find(".", i)) != std::string::npos) {
1890 fname_base.replace(i, 4, normal_ext);
1891 i += normal_ext.length();
1893 return getTexture(fname_base, &id);