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);
394 // The id of the thread that is allowed to use irrlicht directly
395 threadid_t m_main_thread;
396 // The irrlicht device
397 IrrlichtDevice *m_device;
399 // Cache of source images
400 // This should be only accessed from the main thread
401 SourceImageCache m_sourcecache;
403 // Thread-safe cache of what source images are known (true = known)
404 MutexedMap<std::string, bool> m_source_image_existence;
406 // A texture id is index in this array.
407 // The first position contains a NULL texture.
408 std::vector<TextureInfo> m_textureinfo_cache;
409 // Maps a texture name to an index in the former.
410 std::map<std::string, u32> m_name_to_id;
411 // The two former containers are behind this mutex
412 JMutex m_textureinfo_cache_mutex;
414 // Queued texture fetches (to be processed by the main thread)
415 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
417 // Textures that have been overwritten with other ones
418 // but can't be deleted because the ITexture* might still be used
419 std::list<video::ITexture*> m_texture_trash;
421 // Cached settings needed for making textures from meshes
422 bool m_setting_trilinear_filter;
423 bool m_setting_bilinear_filter;
424 bool m_setting_anisotropic_filter;
427 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
429 return new TextureSource(device);
432 TextureSource::TextureSource(IrrlichtDevice *device):
437 m_main_thread = get_current_thread_id();
439 // Add a NULL TextureInfo as the first index, named ""
440 m_textureinfo_cache.push_back(TextureInfo(""));
441 m_name_to_id[""] = 0;
443 // Cache some settings
444 // Note: Since this is only done once, the game must be restarted
445 // for these settings to take effect
446 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
447 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
448 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
451 TextureSource::~TextureSource()
453 video::IVideoDriver* driver = m_device->getVideoDriver();
455 unsigned int textures_before = driver->getTextureCount();
457 for (std::vector<TextureInfo>::iterator iter =
458 m_textureinfo_cache.begin();
459 iter != m_textureinfo_cache.end(); iter++)
463 driver->removeTexture(iter->texture);
465 m_textureinfo_cache.clear();
467 for (std::list<video::ITexture*>::iterator iter =
468 m_texture_trash.begin(); iter != m_texture_trash.end();
471 video::ITexture *t = *iter;
473 //cleanup trashed texture
474 driver->removeTexture(t);
477 infostream << "~TextureSource() "<< textures_before << "/"
478 << driver->getTextureCount() << std::endl;
481 u32 TextureSource::getTextureId(const std::string &name)
483 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
487 See if texture already exists
489 JMutexAutoLock lock(m_textureinfo_cache_mutex);
490 std::map<std::string, u32>::iterator n;
491 n = m_name_to_id.find(name);
492 if(n != m_name_to_id.end())
501 if(get_current_thread_id() == m_main_thread)
503 return getTextureIdDirect(name);
507 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
509 // We're gonna ask the result to be put into here
510 static ResultQueue<std::string, u32, u8, u8> result_queue;
512 // Throw a request in
513 m_get_texture_queue.add(name, 0, 0, &result_queue);
515 /*infostream<<"Waiting for texture from main thread, name=\""
516 <<name<<"\""<<std::endl;*/
521 // Wait result for a second
522 GetResult<std::string, u32, u8, u8>
523 result = result_queue.pop_front(1000);
525 if (result.key == name) {
530 catch(ItemNotFoundException &e)
532 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
537 infostream<<"getTextureId(): Failed"<<std::endl;
542 // Draw an image on top of an another one, using the alpha channel of the
544 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
545 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
547 // Like blit_with_alpha, but only modifies destination pixels that
549 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
550 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552 // Draw or overlay a crack
553 static void draw_crack(video::IImage *crack, video::IImage *dst,
554 bool use_overlay, s32 frame_count, s32 progression,
555 video::IVideoDriver *driver);
558 void brighten(video::IImage *image);
559 // Parse a transform name
560 u32 parseImageTransform(const std::string& s);
561 // Apply transform to image dimension
562 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
563 // Apply transform to image data
564 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
567 This method generates all the textures
569 u32 TextureSource::getTextureIdDirect(const std::string &name)
571 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
573 // Empty name means texture 0
576 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
581 Calling only allowed from main thread
583 if(get_current_thread_id() != m_main_thread)
585 errorstream<<"TextureSource::getTextureIdDirect() "
586 "called not from main thread"<<std::endl;
591 See if texture already exists
594 JMutexAutoLock lock(m_textureinfo_cache_mutex);
596 std::map<std::string, u32>::iterator n;
597 n = m_name_to_id.find(name);
598 if(n != m_name_to_id.end())
600 /*infostream<<"getTextureIdDirect(): \""<<name
601 <<"\" found in cache"<<std::endl;*/
606 /*infostream<<"getTextureIdDirect(): \""<<name
607 <<"\" NOT found in cache. Creating it."<<std::endl;*/
613 char separator = '^';
616 This is set to the id of the base image.
617 If left 0, there is no base image and a completely new image
620 u32 base_image_id = 0;
622 // Find last meta separator in name
623 s32 last_separator_position = -1;
624 for(s32 i=name.size()-1; i>=0; i--)
626 if(name[i] == separator)
628 last_separator_position = i;
633 If separator was found, construct the base name and make the
634 base image using a recursive call
636 std::string base_image_name;
637 if(last_separator_position != -1)
639 // Construct base name
640 base_image_name = name.substr(0, last_separator_position);
641 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
642 " to get base image of \""<<name<<"\" = \""
643 <<base_image_name<<"\""<<std::endl;*/
644 base_image_id = getTextureIdDirect(base_image_name);
647 //infostream<<"base_image_id="<<base_image_id<<std::endl;
649 video::IVideoDriver* driver = m_device->getVideoDriver();
652 video::ITexture *t = NULL;
655 An image will be built from files and then converted into a texture.
657 video::IImage *baseimg = NULL;
659 // If a base image was found, copy it to baseimg
660 if(base_image_id != 0)
662 JMutexAutoLock lock(m_textureinfo_cache_mutex);
664 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
666 if(ti->texture == NULL)
668 infostream<<"getTextureIdDirect(): WARNING: NULL Texture in "
669 <<"cache: \""<<base_image_name<<"\""
674 core::dimension2d<u32> dim = ti->texture->getSize();
676 baseimg = driver->createImage(ti->texture,v2s32(0,0), dim);
678 /*infostream<<"getTextureIdDirect(): Loaded \""
679 <<base_image_name<<"\" from image cache"
685 Parse out the last part of the name of the image and act
689 std::string last_part_of_name = name.substr(last_separator_position+1);
690 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
692 // Generate image according to part of name
693 if(!generateImage(last_part_of_name, baseimg))
695 errorstream<<"getTextureIdDirect(): "
696 "failed to generate \""<<last_part_of_name<<"\""
700 // If no resulting image, print a warning
703 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
704 " create texture \""<<name<<"\""<<std::endl;
710 baseimg = Align2Npot2(baseimg, driver);
712 // Create texture from resulting image
713 t = driver->addTexture(name.c_str(), baseimg);
718 Add texture to caches (add NULL textures too)
721 JMutexAutoLock lock(m_textureinfo_cache_mutex);
723 u32 id = m_textureinfo_cache.size();
724 TextureInfo ti(name, t);
725 m_textureinfo_cache.push_back(ti);
726 m_name_to_id[name] = id;
731 std::string TextureSource::getTextureName(u32 id)
733 JMutexAutoLock lock(m_textureinfo_cache_mutex);
735 if(id >= m_textureinfo_cache.size())
737 errorstream<<"TextureSource::getTextureName(): id="<<id
738 <<" >= m_textureinfo_cache.size()="
739 <<m_textureinfo_cache.size()<<std::endl;
743 return m_textureinfo_cache[id].name;
746 video::ITexture* TextureSource::getTexture(u32 id)
748 JMutexAutoLock lock(m_textureinfo_cache_mutex);
750 if(id >= m_textureinfo_cache.size())
753 return m_textureinfo_cache[id].texture;
756 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
758 u32 actual_id = getTextureId(name);
762 return getTexture(actual_id);
765 void TextureSource::processQueue()
770 //NOTE this is only thread safe for ONE consumer thread!
771 if(!m_get_texture_queue.empty())
773 GetRequest<std::string, u32, u8, u8>
774 request = m_get_texture_queue.pop();
776 /*infostream<<"TextureSource::processQueue(): "
777 <<"got texture request with "
778 <<"name=\""<<request.key<<"\""
781 m_get_texture_queue.pushResult(request,getTextureIdDirect(request.key));
785 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
787 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
789 assert(get_current_thread_id() == m_main_thread);
791 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
792 m_source_image_existence.set(name, true);
795 void TextureSource::rebuildImagesAndTextures()
797 JMutexAutoLock lock(m_textureinfo_cache_mutex);
799 video::IVideoDriver* driver = m_device->getVideoDriver();
803 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
804 TextureInfo *ti = &m_textureinfo_cache[i];
805 video::IImage *img = generateImageFromScratch(ti->name);
807 img = Align2Npot2(img,driver);
808 assert(img->getDimension().Height == npot2(img->getDimension().Height));
809 assert(img->getDimension().Width == npot2(img->getDimension().Width));
811 // Create texture from resulting image
812 video::ITexture *t = NULL;
814 t = driver->addTexture(ti->name.c_str(), img);
817 video::ITexture *t_old = ti->texture;
822 m_texture_trash.push_back(t_old);
826 video::ITexture* TextureSource::generateTextureFromMesh(
827 const TextureFromMeshParams ¶ms)
829 video::IVideoDriver *driver = m_device->getVideoDriver();
833 const GLubyte* renderstr = glGetString(GL_RENDERER);
834 std::string renderer((char*) renderstr);
836 // use no render to texture hack
838 (renderer.find("Adreno") != std::string::npos) ||
839 (renderer.find("Mali") != std::string::npos) ||
840 (renderer.find("Immersion") != std::string::npos) ||
841 (renderer.find("Tegra") != std::string::npos) ||
842 g_settings->getBool("inventory_image_hack")
844 // Get a scene manager
845 scene::ISceneManager *smgr_main = m_device->getSceneManager();
847 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
850 const float scaling = 0.2;
852 scene::IMeshSceneNode* meshnode =
853 smgr->addMeshSceneNode(params.mesh, NULL,
854 -1, v3f(0,0,0), v3f(0,0,0),
855 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
856 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
857 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
858 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
859 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
860 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
862 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
863 params.camera_position, params.camera_lookat);
864 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
865 camera->setProjectionMatrix(params.camera_projection_matrix, false);
867 smgr->setAmbientLight(params.ambient_light);
868 smgr->addLightSceneNode(0,
869 params.light_position,
871 params.light_radius*scaling);
873 core::dimension2d<u32> screen = driver->getScreenSize();
876 driver->beginScene(true, true, video::SColor(0,0,0,0));
877 driver->clearZBuffer();
880 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
882 irr::video::IImage* rawImage =
883 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
885 u8* pixels = static_cast<u8*>(rawImage->lock());
892 core::rect<s32> source(
893 screen.Width /2 - (screen.Width * (scaling / 2)),
894 screen.Height/2 - (screen.Height * (scaling / 2)),
895 screen.Width /2 + (screen.Width * (scaling / 2)),
896 screen.Height/2 + (screen.Height * (scaling / 2))
899 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
900 partsize.Width, partsize.Height, GL_RGBA,
901 GL_UNSIGNED_BYTE, pixels);
905 // Drop scene manager
908 unsigned int pixelcount = partsize.Width*partsize.Height;
911 for (unsigned int i=0; i < pixelcount; i++) {
929 video::IImage* inventory_image =
930 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
932 rawImage->copyToScaling(inventory_image);
935 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
936 inventory_image->drop();
939 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
943 driver->makeColorKeyTexture(rtt, v2s32(0,0));
945 if(params.delete_texture_on_shutdown)
946 m_texture_trash.push_back(rtt);
952 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
954 static bool warned = false;
957 errorstream<<"TextureSource::generateTextureFromMesh(): "
958 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
964 // Create render target texture
965 video::ITexture *rtt = driver->addRenderTargetTexture(
966 params.dim, params.rtt_texture_name.c_str(),
967 video::ECF_A8R8G8B8);
970 errorstream<<"TextureSource::generateTextureFromMesh(): "
971 <<"addRenderTargetTexture returned NULL."<<std::endl;
976 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
977 driver->removeTexture(rtt);
978 errorstream<<"TextureSource::generateTextureFromMesh(): "
979 <<"failed to set render target"<<std::endl;
983 // Get a scene manager
984 scene::ISceneManager *smgr_main = m_device->getSceneManager();
986 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
989 scene::IMeshSceneNode* meshnode =
990 smgr->addMeshSceneNode(params.mesh, NULL,
991 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
992 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
993 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
994 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
995 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
996 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
998 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
999 params.camera_position, params.camera_lookat);
1000 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
1001 camera->setProjectionMatrix(params.camera_projection_matrix, false);
1003 smgr->setAmbientLight(params.ambient_light);
1004 smgr->addLightSceneNode(0,
1005 params.light_position,
1007 params.light_radius);
1010 driver->beginScene(true, true, video::SColor(0,0,0,0));
1014 // Drop scene manager
1017 // Unset render target
1018 driver->setRenderTarget(0, false, true, 0);
1020 if(params.delete_texture_on_shutdown)
1021 m_texture_trash.push_back(rtt);
1026 video::IImage* TextureSource::generateImageFromScratch(std::string name)
1028 /*infostream<<"generateImageFromScratch(): "
1029 "\""<<name<<"\""<<std::endl;*/
1031 video::IVideoDriver *driver = m_device->getVideoDriver();
1038 video::IImage *baseimg = NULL;
1040 char separator = '^';
1042 // Find last meta separator in name
1043 s32 last_separator_position = name.find_last_of(separator);
1046 If separator was found, construct the base name and make the
1047 base image using a recursive call
1049 std::string base_image_name;
1050 if(last_separator_position != -1)
1052 // Construct base name
1053 base_image_name = name.substr(0, last_separator_position);
1054 baseimg = generateImageFromScratch(base_image_name);
1058 Parse out the last part of the name of the image and act
1062 std::string last_part_of_name = name.substr(last_separator_position+1);
1064 // Generate image according to part of name
1065 if(!generateImage(last_part_of_name, baseimg))
1067 errorstream<<"generateImageFromScratch(): "
1068 "failed to generate \""<<last_part_of_name<<"\""
1077 #include <GLES/gl.h>
1079 * Check and align image to npot2 if required by hardware
1080 * @param image image to check for npot2 alignment
1081 * @param driver driver to use for image operations
1082 * @return image or copy of image aligned to npot2
1084 video::IImage * Align2Npot2(video::IImage * image,
1085 video::IVideoDriver* driver)
1091 core::dimension2d<u32> dim = image->getDimension();
1093 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1094 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1098 unsigned int height = npot2(dim.Height);
1099 unsigned int width = npot2(dim.Width);
1101 if ((dim.Height == height) &&
1102 (dim.Width == width)) {
1106 if (dim.Height > height) {
1110 if (dim.Width > width) {
1114 video::IImage *targetimage =
1115 driver->createImage(video::ECF_A8R8G8B8,
1116 core::dimension2d<u32>(width, height));
1118 if (targetimage != NULL) {
1119 image->copyToScaling(targetimage);
1127 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
1129 video::IVideoDriver* driver = m_device->getVideoDriver();
1132 // Stuff starting with [ are special commands
1133 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1135 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1137 image = Align2Npot2(image,driver);
1139 if (image == NULL) {
1140 if (part_of_name != "") {
1141 if (part_of_name.find("_normal.png") == std::string::npos){
1142 errorstream<<"generateImage(): Could not load image \""
1143 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1144 errorstream<<"generateImage(): Creating a dummy"
1145 <<" image for \""<<part_of_name<<"\""<<std::endl;
1147 infostream<<"generateImage(): Could not load normal map \""
1148 <<part_of_name<<"\""<<std::endl;
1149 infostream<<"generateImage(): Creating a dummy"
1150 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1154 // Just create a dummy image
1155 //core::dimension2d<u32> dim(2,2);
1156 core::dimension2d<u32> dim(1,1);
1157 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1159 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1160 image->setPixel(1,0, video::SColor(255,0,255,0));
1161 image->setPixel(0,1, video::SColor(255,0,0,255));
1162 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1163 image->setPixel(0,0, video::SColor(255,myrand()%256,
1164 myrand()%256,myrand()%256));
1165 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1166 myrand()%256,myrand()%256));
1167 image->setPixel(0,1, video::SColor(255,myrand()%256,
1168 myrand()%256,myrand()%256));
1169 image->setPixel(1,1, video::SColor(255,myrand()%256,
1170 myrand()%256,myrand()%256));*/
1173 // If base image is NULL, load as base.
1176 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1178 Copy it this way to get an alpha channel.
1179 Otherwise images with alpha cannot be blitted on
1180 images that don't have alpha in the original file.
1182 core::dimension2d<u32> dim = image->getDimension();
1183 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1184 image->copyTo(baseimg);
1186 // Else blit on base.
1189 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1190 // Size of the copied area
1191 core::dimension2d<u32> dim = image->getDimension();
1192 //core::dimension2d<u32> dim(16,16);
1193 // Position to copy the blitted to in the base image
1194 core::position2d<s32> pos_to(0,0);
1195 // Position to copy the blitted from in the blitted image
1196 core::position2d<s32> pos_from(0,0);
1198 /*image->copyToWithAlpha(baseimg, pos_to,
1199 core::rect<s32>(pos_from, dim),
1200 video::SColor(255,255,255,255),
1202 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1209 // A special texture modification
1211 /*infostream<<"generateImage(): generating special "
1212 <<"modification \""<<part_of_name<<"\""
1218 Adds a cracking texture
1219 N = animation frame count, P = crack progression
1221 if(part_of_name.substr(0,6) == "[crack")
1225 errorstream<<"generateImage(): baseimg==NULL "
1226 <<"for part_of_name=\""<<part_of_name
1227 <<"\", cancelling."<<std::endl;
1231 // Crack image number and overlay option
1232 bool use_overlay = (part_of_name[6] == 'o');
1233 Strfnd sf(part_of_name);
1235 s32 frame_count = stoi(sf.next(":"));
1236 s32 progression = stoi(sf.next(":"));
1241 It is an image with a number of cracking stages
1244 video::IImage *img_crack = m_sourcecache.getOrLoad(
1245 "crack_anylength.png", m_device);
1247 if(img_crack && progression >= 0)
1249 draw_crack(img_crack, baseimg,
1250 use_overlay, frame_count,
1251 progression, driver);
1256 [combine:WxH:X,Y=filename:X,Y=filename2
1257 Creates a bigger texture from an amount of smaller ones
1259 else if(part_of_name.substr(0,8) == "[combine")
1261 Strfnd sf(part_of_name);
1263 u32 w0 = stoi(sf.next("x"));
1264 u32 h0 = stoi(sf.next(":"));
1265 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1266 core::dimension2d<u32> dim(w0,h0);
1269 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1270 baseimg->fill(video::SColor(0,0,0,0));
1272 while(sf.atend() == false)
1274 u32 x = stoi(sf.next(","));
1275 u32 y = stoi(sf.next("="));
1276 std::string filename = sf.next(":");
1277 infostream<<"Adding \""<<filename
1278 <<"\" to combined ("<<x<<","<<y<<")"
1280 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1283 core::dimension2d<u32> dim = img->getDimension();
1284 infostream<<"Size "<<dim.Width
1285 <<"x"<<dim.Height<<std::endl;
1286 core::position2d<s32> pos_base(x, y);
1287 video::IImage *img2 =
1288 driver->createImage(video::ECF_A8R8G8B8, dim);
1291 /*img2->copyToWithAlpha(baseimg, pos_base,
1292 core::rect<s32>(v2s32(0,0), dim),
1293 video::SColor(255,255,255,255),
1295 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1300 infostream<<"img==NULL"<<std::endl;
1307 else if(part_of_name.substr(0,9) == "[brighten")
1311 errorstream<<"generateImage(): baseimg==NULL "
1312 <<"for part_of_name=\""<<part_of_name
1313 <<"\", cancelling."<<std::endl;
1321 Make image completely opaque.
1322 Used for the leaves texture when in old leaves mode, so
1323 that the transparent parts don't look completely black
1324 when simple alpha channel is used for rendering.
1326 else if(part_of_name.substr(0,8) == "[noalpha")
1330 errorstream<<"generateImage(): baseimg==NULL "
1331 <<"for part_of_name=\""<<part_of_name
1332 <<"\", cancelling."<<std::endl;
1336 core::dimension2d<u32> dim = baseimg->getDimension();
1338 // Set alpha to full
1339 for(u32 y=0; y<dim.Height; y++)
1340 for(u32 x=0; x<dim.Width; x++)
1342 video::SColor c = baseimg->getPixel(x,y);
1344 baseimg->setPixel(x,y,c);
1349 Convert one color to transparent.
1351 else if(part_of_name.substr(0,11) == "[makealpha:")
1355 errorstream<<"generateImage(): baseimg==NULL "
1356 <<"for part_of_name=\""<<part_of_name
1357 <<"\", cancelling."<<std::endl;
1361 Strfnd sf(part_of_name.substr(11));
1362 u32 r1 = stoi(sf.next(","));
1363 u32 g1 = stoi(sf.next(","));
1364 u32 b1 = stoi(sf.next(""));
1365 std::string filename = sf.next("");
1367 core::dimension2d<u32> dim = baseimg->getDimension();
1369 /*video::IImage *oldbaseimg = baseimg;
1370 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1371 oldbaseimg->copyTo(baseimg);
1372 oldbaseimg->drop();*/
1374 // Set alpha to full
1375 for(u32 y=0; y<dim.Height; y++)
1376 for(u32 x=0; x<dim.Width; x++)
1378 video::SColor c = baseimg->getPixel(x,y);
1380 u32 g = c.getGreen();
1381 u32 b = c.getBlue();
1382 if(!(r == r1 && g == g1 && b == b1))
1385 baseimg->setPixel(x,y,c);
1390 Rotates and/or flips the image.
1392 N can be a number (between 0 and 7) or a transform name.
1393 Rotations are counter-clockwise.
1395 1 R90 rotate by 90 degrees
1396 2 R180 rotate by 180 degrees
1397 3 R270 rotate by 270 degrees
1399 5 FXR90 flip X then rotate by 90 degrees
1401 7 FYR90 flip Y then rotate by 90 degrees
1403 Note: Transform names can be concatenated to produce
1404 their product (applies the first then the second).
1405 The resulting transform will be equivalent to one of the
1406 eight existing ones, though (see: dihedral group).
1408 else if(part_of_name.substr(0,10) == "[transform")
1412 errorstream<<"generateImage(): baseimg==NULL "
1413 <<"for part_of_name=\""<<part_of_name
1414 <<"\", cancelling."<<std::endl;
1418 u32 transform = parseImageTransform(part_of_name.substr(10));
1419 core::dimension2d<u32> dim = imageTransformDimension(
1420 transform, baseimg->getDimension());
1421 video::IImage *image = driver->createImage(
1422 baseimg->getColorFormat(), dim);
1424 imageTransform(transform, baseimg, image);
1429 [inventorycube{topimage{leftimage{rightimage
1430 In every subimage, replace ^ with &.
1431 Create an "inventory cube".
1432 NOTE: This should be used only on its own.
1433 Example (a grass block (not actually used in game):
1434 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1436 else if(part_of_name.substr(0,14) == "[inventorycube")
1440 errorstream<<"generateImage(): baseimg!=NULL "
1441 <<"for part_of_name=\""<<part_of_name
1442 <<"\", cancelling."<<std::endl;
1446 str_replace_char(part_of_name, '&', '^');
1447 Strfnd sf(part_of_name);
1449 std::string imagename_top = sf.next("{");
1450 std::string imagename_left = sf.next("{");
1451 std::string imagename_right = sf.next("{");
1453 // Generate images for the faces of the cube
1454 video::IImage *img_top =
1455 generateImageFromScratch(imagename_top);
1456 video::IImage *img_left =
1457 generateImageFromScratch(imagename_left);
1458 video::IImage *img_right =
1459 generateImageFromScratch(imagename_right);
1460 assert(img_top && img_left && img_right);
1462 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1463 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1465 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1466 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1468 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1469 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1471 // Create textures from images
1472 video::ITexture *texture_top = driver->addTexture(
1473 (imagename_top + "__temp__").c_str(), img_top);
1474 video::ITexture *texture_left = driver->addTexture(
1475 (imagename_left + "__temp__").c_str(), img_left);
1476 video::ITexture *texture_right = driver->addTexture(
1477 (imagename_right + "__temp__").c_str(), img_right);
1478 assert(texture_top && texture_left && texture_right);
1486 Draw a cube mesh into a render target texture
1488 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1489 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1490 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1491 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1492 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1493 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1494 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1495 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1497 TextureFromMeshParams params;
1499 params.dim.set(64, 64);
1500 params.rtt_texture_name = part_of_name + "_RTT";
1501 // We will delete the rtt texture ourselves
1502 params.delete_texture_on_shutdown = false;
1503 params.camera_position.set(0, 1.0, -1.5);
1504 params.camera_position.rotateXZBy(45);
1505 params.camera_lookat.set(0, 0, 0);
1506 // Set orthogonal projection
1507 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1508 1.65, 1.65, 0, 100);
1510 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1511 params.light_position.set(10, 100, -50);
1512 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1513 params.light_radius = 1000;
1515 video::ITexture *rtt = generateTextureFromMesh(params);
1520 // Free textures of images
1521 driver->removeTexture(texture_top);
1522 driver->removeTexture(texture_left);
1523 driver->removeTexture(texture_right);
1527 baseimg = generateImageFromScratch(imagename_top);
1531 // Create image of render target
1532 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1536 driver->removeTexture(rtt);
1538 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1542 image->copyTo(baseimg);
1547 [lowpart:percent:filename
1548 Adds the lower part of a texture
1550 else if(part_of_name.substr(0,9) == "[lowpart:")
1552 Strfnd sf(part_of_name);
1554 u32 percent = stoi(sf.next(":"));
1555 std::string filename = sf.next(":");
1556 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1559 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1560 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1563 core::dimension2d<u32> dim = img->getDimension();
1564 core::position2d<s32> pos_base(0, 0);
1565 video::IImage *img2 =
1566 driver->createImage(video::ECF_A8R8G8B8, dim);
1569 core::position2d<s32> clippos(0, 0);
1570 clippos.Y = dim.Height * (100-percent) / 100;
1571 core::dimension2d<u32> clipdim = dim;
1572 clipdim.Height = clipdim.Height * percent / 100 + 1;
1573 core::rect<s32> cliprect(clippos, clipdim);
1574 img2->copyToWithAlpha(baseimg, pos_base,
1575 core::rect<s32>(v2s32(0,0), dim),
1576 video::SColor(255,255,255,255),
1583 Crops a frame of a vertical animation.
1584 N = frame count, I = frame index
1586 else if(part_of_name.substr(0,15) == "[verticalframe:")
1588 Strfnd sf(part_of_name);
1590 u32 frame_count = stoi(sf.next(":"));
1591 u32 frame_index = stoi(sf.next(":"));
1593 if(baseimg == NULL){
1594 errorstream<<"generateImage(): baseimg!=NULL "
1595 <<"for part_of_name=\""<<part_of_name
1596 <<"\", cancelling."<<std::endl;
1600 v2u32 frame_size = baseimg->getDimension();
1601 frame_size.Y /= frame_count;
1603 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1606 errorstream<<"generateImage(): Could not create image "
1607 <<"for part_of_name=\""<<part_of_name
1608 <<"\", cancelling."<<std::endl;
1612 // Fill target image with transparency
1613 img->fill(video::SColor(0,0,0,0));
1615 core::dimension2d<u32> dim = frame_size;
1616 core::position2d<s32> pos_dst(0, 0);
1617 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1618 baseimg->copyToWithAlpha(img, pos_dst,
1619 core::rect<s32>(pos_src, dim),
1620 video::SColor(255,255,255,255),
1628 errorstream<<"generateImage(): Invalid "
1629 " modification: \""<<part_of_name<<"\""<<std::endl;
1637 Draw an image on top of an another one, using the alpha channel of the
1640 This exists because IImage::copyToWithAlpha() doesn't seem to always
1643 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1644 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1646 for(u32 y0=0; y0<size.Y; y0++)
1647 for(u32 x0=0; x0<size.X; x0++)
1649 s32 src_x = src_pos.X + x0;
1650 s32 src_y = src_pos.Y + y0;
1651 s32 dst_x = dst_pos.X + x0;
1652 s32 dst_y = dst_pos.Y + y0;
1653 video::SColor src_c = src->getPixel(src_x, src_y);
1654 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1655 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1656 dst->setPixel(dst_x, dst_y, dst_c);
1661 Draw an image on top of an another one, using the alpha channel of the
1662 source image; only modify fully opaque pixels in destinaion
1664 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1665 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1667 for(u32 y0=0; y0<size.Y; y0++)
1668 for(u32 x0=0; x0<size.X; x0++)
1670 s32 src_x = src_pos.X + x0;
1671 s32 src_y = src_pos.Y + y0;
1672 s32 dst_x = dst_pos.X + x0;
1673 s32 dst_y = dst_pos.Y + y0;
1674 video::SColor src_c = src->getPixel(src_x, src_y);
1675 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1676 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1678 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1679 dst->setPixel(dst_x, dst_y, dst_c);
1684 static void draw_crack(video::IImage *crack, video::IImage *dst,
1685 bool use_overlay, s32 frame_count, s32 progression,
1686 video::IVideoDriver *driver)
1688 // Dimension of destination image
1689 core::dimension2d<u32> dim_dst = dst->getDimension();
1690 // Dimension of original image
1691 core::dimension2d<u32> dim_crack = crack->getDimension();
1692 // Count of crack stages
1693 s32 crack_count = dim_crack.Height / dim_crack.Width;
1694 // Limit frame_count
1695 if(frame_count > (s32) dim_dst.Height)
1696 frame_count = dim_dst.Height;
1699 // Limit progression
1700 if(progression > crack_count-1)
1701 progression = crack_count-1;
1702 // Dimension of a single crack stage
1703 core::dimension2d<u32> dim_crack_cropped(
1707 // Dimension of the scaled crack stage,
1708 // which is the same as the dimension of a single destination frame
1709 core::dimension2d<u32> dim_crack_scaled(
1711 dim_dst.Height / frame_count
1713 // Create cropped and scaled crack images
1714 video::IImage *crack_cropped = driver->createImage(
1715 video::ECF_A8R8G8B8, dim_crack_cropped);
1716 video::IImage *crack_scaled = driver->createImage(
1717 video::ECF_A8R8G8B8, dim_crack_scaled);
1719 if(crack_cropped && crack_scaled)
1722 v2s32 pos_crack(0, progression*dim_crack.Width);
1723 crack->copyTo(crack_cropped,
1725 core::rect<s32>(pos_crack, dim_crack_cropped));
1726 // Scale crack image by copying
1727 crack_cropped->copyToScaling(crack_scaled);
1728 // Copy or overlay crack image onto each frame
1729 for(s32 i = 0; i < frame_count; ++i)
1731 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1734 blit_with_alpha_overlay(crack_scaled, dst,
1735 v2s32(0,0), dst_pos,
1740 blit_with_alpha(crack_scaled, dst,
1741 v2s32(0,0), dst_pos,
1748 crack_scaled->drop();
1751 crack_cropped->drop();
1754 void brighten(video::IImage *image)
1759 core::dimension2d<u32> dim = image->getDimension();
1761 for(u32 y=0; y<dim.Height; y++)
1762 for(u32 x=0; x<dim.Width; x++)
1764 video::SColor c = image->getPixel(x,y);
1765 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1766 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1767 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1768 image->setPixel(x,y,c);
1772 u32 parseImageTransform(const std::string& s)
1774 int total_transform = 0;
1776 std::string transform_names[8];
1777 transform_names[0] = "i";
1778 transform_names[1] = "r90";
1779 transform_names[2] = "r180";
1780 transform_names[3] = "r270";
1781 transform_names[4] = "fx";
1782 transform_names[6] = "fy";
1784 std::size_t pos = 0;
1785 while(pos < s.size())
1788 for(int i = 0; i <= 7; ++i)
1790 const std::string &name_i = transform_names[i];
1792 if(s[pos] == ('0' + i))
1798 else if(!(name_i.empty()) &&
1799 lowercase(s.substr(pos, name_i.size())) == name_i)
1802 pos += name_i.size();
1809 // Multiply total_transform and transform in the group D4
1812 new_total = (transform + total_transform) % 4;
1814 new_total = (transform - total_transform + 8) % 4;
1815 if((transform >= 4) ^ (total_transform >= 4))
1818 total_transform = new_total;
1820 return total_transform;
1823 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1825 if(transform % 2 == 0)
1828 return core::dimension2d<u32>(dim.Height, dim.Width);
1831 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1833 if(src == NULL || dst == NULL)
1836 core::dimension2d<u32> srcdim = src->getDimension();
1837 core::dimension2d<u32> dstdim = dst->getDimension();
1839 assert(dstdim == imageTransformDimension(transform, srcdim));
1840 assert(transform >= 0 && transform <= 7);
1843 Compute the transformation from source coordinates (sx,sy)
1844 to destination coordinates (dx,dy).
1848 if(transform == 0) // identity
1849 sxn = 0, syn = 2; // sx = dx, sy = dy
1850 else if(transform == 1) // rotate by 90 degrees ccw
1851 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1852 else if(transform == 2) // rotate by 180 degrees
1853 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1854 else if(transform == 3) // rotate by 270 degrees ccw
1855 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1856 else if(transform == 4) // flip x
1857 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1858 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1859 sxn = 2, syn = 0; // sx = dy, sy = dx
1860 else if(transform == 6) // flip y
1861 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1862 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1863 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1865 for(u32 dy=0; dy<dstdim.Height; dy++)
1866 for(u32 dx=0; dx<dstdim.Width; dx++)
1868 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1869 u32 sx = entries[sxn];
1870 u32 sy = entries[syn];
1871 video::SColor c = src->getPixel(sx,sy);
1872 dst->setPixel(dx,dy,c);