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, generates the texture, adds to cache and returns id.
310 - if other thread, adds to request queue and waits for main thread.
312 The id 0 points to a NULL texture. It is returned in case of error.
314 u32 getTextureId(const std::string &name);
316 // Finds out the name of a cached texture.
317 std::string getTextureName(u32 id);
320 If texture specified by the name pointed by the id doesn't
321 exist, create it, then return the cached texture.
323 Can be called from any thread. If called from some other thread
324 and not found in cache, the call is queued to the main thread
327 video::ITexture* getTexture(u32 id);
329 video::ITexture* getTexture(const std::string &name, u32 *id);
331 // Returns a pointer to the irrlicht device
332 virtual IrrlichtDevice* getDevice()
337 bool isKnownSourceImage(const std::string &name)
339 bool is_known = false;
340 bool cache_found = m_source_image_existence.get(name, &is_known);
343 // Not found in cache; find out if a local file exists
344 is_known = (getTexturePath(name) != "");
345 m_source_image_existence.set(name, is_known);
349 // Processes queued texture requests from other threads.
350 // Shall be called from the main thread.
353 // Insert an image into the cache without touching the filesystem.
354 // Shall be called from the main thread.
355 void insertSourceImage(const std::string &name, video::IImage *img);
357 // Rebuild images and textures from the current set of source images
358 // Shall be called from the main thread.
359 void rebuildImagesAndTextures();
361 // Render a mesh to a texture.
362 // Returns NULL if render-to-texture failed.
363 // Shall be called from the main thread.
364 video::ITexture* generateTextureFromMesh(
365 const TextureFromMeshParams ¶ms);
367 // Generates an image from a full string like
368 // "stone.png^mineral_coal.png^[crack:1:0".
369 // Shall be called from the main thread.
370 video::IImage* generateImage(const std::string &name);
372 video::ITexture* getNormalTexture(const std::string &name);
375 // The id of the thread that is allowed to use irrlicht directly
376 threadid_t m_main_thread;
377 // The irrlicht device
378 IrrlichtDevice *m_device;
380 // Cache of source images
381 // This should be only accessed from the main thread
382 SourceImageCache m_sourcecache;
384 // Generate a texture
385 u32 generateTexture(const 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 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
391 // Thread-safe cache of what source images are known (true = known)
392 MutexedMap<std::string, bool> m_source_image_existence;
394 // A texture id is index in this array.
395 // The first position contains a NULL texture.
396 std::vector<TextureInfo> m_textureinfo_cache;
397 // Maps a texture name to an index in the former.
398 std::map<std::string, u32> m_name_to_id;
399 // The two former containers are behind this mutex
400 JMutex m_textureinfo_cache_mutex;
402 // Queued texture fetches (to be processed by the main thread)
403 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
405 // Textures that have been overwritten with other ones
406 // but can't be deleted because the ITexture* might still be used
407 std::list<video::ITexture*> m_texture_trash;
409 // Cached settings needed for making textures from meshes
410 bool m_setting_trilinear_filter;
411 bool m_setting_bilinear_filter;
412 bool m_setting_anisotropic_filter;
415 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
417 return new TextureSource(device);
420 TextureSource::TextureSource(IrrlichtDevice *device):
425 m_main_thread = get_current_thread_id();
427 // Add a NULL TextureInfo as the first index, named ""
428 m_textureinfo_cache.push_back(TextureInfo(""));
429 m_name_to_id[""] = 0;
431 // Cache some settings
432 // Note: Since this is only done once, the game must be restarted
433 // for these settings to take effect
434 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
435 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
436 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
439 TextureSource::~TextureSource()
441 video::IVideoDriver* driver = m_device->getVideoDriver();
443 unsigned int textures_before = driver->getTextureCount();
445 for (std::vector<TextureInfo>::iterator iter =
446 m_textureinfo_cache.begin();
447 iter != m_textureinfo_cache.end(); iter++)
451 driver->removeTexture(iter->texture);
453 m_textureinfo_cache.clear();
455 for (std::list<video::ITexture*>::iterator iter =
456 m_texture_trash.begin(); iter != m_texture_trash.end();
459 video::ITexture *t = *iter;
461 //cleanup trashed texture
462 driver->removeTexture(t);
465 infostream << "~TextureSource() "<< textures_before << "/"
466 << driver->getTextureCount() << std::endl;
469 u32 TextureSource::getTextureId(const std::string &name)
471 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
475 See if texture already exists
477 JMutexAutoLock lock(m_textureinfo_cache_mutex);
478 std::map<std::string, u32>::iterator n;
479 n = m_name_to_id.find(name);
480 if(n != m_name_to_id.end())
489 if(get_current_thread_id() == m_main_thread)
491 return generateTexture(name);
495 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
497 // We're gonna ask the result to be put into here
498 static ResultQueue<std::string, u32, u8, u8> result_queue;
500 // Throw a request in
501 m_get_texture_queue.add(name, 0, 0, &result_queue);
503 /*infostream<<"Waiting for texture from main thread, name=\""
504 <<name<<"\""<<std::endl;*/
509 // Wait result for a second
510 GetResult<std::string, u32, u8, u8>
511 result = result_queue.pop_front(1000);
513 if (result.key == name) {
518 catch(ItemNotFoundException &e)
520 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
525 infostream<<"getTextureId(): Failed"<<std::endl;
530 // Draw an image on top of an another one, using the alpha channel of the
532 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
533 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
535 // Like blit_with_alpha, but only modifies destination pixels that
537 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
538 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
540 // Apply a mask to an image
541 static void apply_mask(video::IImage *mask, video::IImage *dst,
542 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
544 // Draw or overlay a crack
545 static void draw_crack(video::IImage *crack, video::IImage *dst,
546 bool use_overlay, s32 frame_count, s32 progression,
547 video::IVideoDriver *driver);
550 void brighten(video::IImage *image);
551 // Parse a transform name
552 u32 parseImageTransform(const std::string& s);
553 // Apply transform to image dimension
554 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
555 // Apply transform to image data
556 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
559 This method generates all the textures
561 u32 TextureSource::generateTexture(const std::string &name)
563 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
565 // Empty name means texture 0
567 infostream<<"generateTexture(): name is empty"<<std::endl;
573 See if texture already exists
575 JMutexAutoLock lock(m_textureinfo_cache_mutex);
576 std::map<std::string, u32>::iterator n;
577 n = m_name_to_id.find(name);
578 if (n != m_name_to_id.end()) {
584 Calling only allowed from main thread
586 if (get_current_thread_id() != m_main_thread) {
587 errorstream<<"TextureSource::generateTexture() "
588 "called not from main thread"<<std::endl;
592 video::IVideoDriver *driver = m_device->getVideoDriver();
595 video::IImage *img = generateImage(name);
597 video::ITexture *tex = NULL;
601 img = Align2Npot2(img, driver);
603 // Create texture from resulting image
604 tex = driver->addTexture(name.c_str(), img);
609 Add texture to caches (add NULL textures too)
612 JMutexAutoLock lock(m_textureinfo_cache_mutex);
614 u32 id = m_textureinfo_cache.size();
615 TextureInfo ti(name, tex);
616 m_textureinfo_cache.push_back(ti);
617 m_name_to_id[name] = id;
622 std::string TextureSource::getTextureName(u32 id)
624 JMutexAutoLock lock(m_textureinfo_cache_mutex);
626 if(id >= m_textureinfo_cache.size())
628 errorstream<<"TextureSource::getTextureName(): id="<<id
629 <<" >= m_textureinfo_cache.size()="
630 <<m_textureinfo_cache.size()<<std::endl;
634 return m_textureinfo_cache[id].name;
637 video::ITexture* TextureSource::getTexture(u32 id)
639 JMutexAutoLock lock(m_textureinfo_cache_mutex);
641 if(id >= m_textureinfo_cache.size())
644 return m_textureinfo_cache[id].texture;
647 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
649 u32 actual_id = getTextureId(name);
653 return getTexture(actual_id);
656 void TextureSource::processQueue()
661 //NOTE this is only thread safe for ONE consumer thread!
662 if(!m_get_texture_queue.empty())
664 GetRequest<std::string, u32, u8, u8>
665 request = m_get_texture_queue.pop();
667 /*infostream<<"TextureSource::processQueue(): "
668 <<"got texture request with "
669 <<"name=\""<<request.key<<"\""
672 m_get_texture_queue.pushResult(request, generateTexture(request.key));
676 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
678 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
680 assert(get_current_thread_id() == m_main_thread);
682 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
683 m_source_image_existence.set(name, true);
686 void TextureSource::rebuildImagesAndTextures()
688 JMutexAutoLock lock(m_textureinfo_cache_mutex);
690 video::IVideoDriver* driver = m_device->getVideoDriver();
694 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
695 TextureInfo *ti = &m_textureinfo_cache[i];
696 video::IImage *img = generateImage(ti->name);
698 img = Align2Npot2(img, driver);
699 assert(img->getDimension().Height == npot2(img->getDimension().Height));
700 assert(img->getDimension().Width == npot2(img->getDimension().Width));
702 // Create texture from resulting image
703 video::ITexture *t = NULL;
705 t = driver->addTexture(ti->name.c_str(), img);
708 video::ITexture *t_old = ti->texture;
713 m_texture_trash.push_back(t_old);
717 video::ITexture* TextureSource::generateTextureFromMesh(
718 const TextureFromMeshParams ¶ms)
720 video::IVideoDriver *driver = m_device->getVideoDriver();
724 const GLubyte* renderstr = glGetString(GL_RENDERER);
725 std::string renderer((char*) renderstr);
727 // use no render to texture hack
729 (renderer.find("Adreno") != std::string::npos) ||
730 (renderer.find("Mali") != std::string::npos) ||
731 (renderer.find("Immersion") != std::string::npos) ||
732 (renderer.find("Tegra") != std::string::npos) ||
733 g_settings->getBool("inventory_image_hack")
735 // Get a scene manager
736 scene::ISceneManager *smgr_main = m_device->getSceneManager();
738 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
741 const float scaling = 0.2;
743 scene::IMeshSceneNode* meshnode =
744 smgr->addMeshSceneNode(params.mesh, NULL,
745 -1, v3f(0,0,0), v3f(0,0,0),
746 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
747 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
748 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
749 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
750 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
751 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
753 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
754 params.camera_position, params.camera_lookat);
755 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
756 camera->setProjectionMatrix(params.camera_projection_matrix, false);
758 smgr->setAmbientLight(params.ambient_light);
759 smgr->addLightSceneNode(0,
760 params.light_position,
762 params.light_radius*scaling);
764 core::dimension2d<u32> screen = driver->getScreenSize();
767 driver->beginScene(true, true, video::SColor(0,0,0,0));
768 driver->clearZBuffer();
771 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
773 irr::video::IImage* rawImage =
774 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
776 u8* pixels = static_cast<u8*>(rawImage->lock());
783 core::rect<s32> source(
784 screen.Width /2 - (screen.Width * (scaling / 2)),
785 screen.Height/2 - (screen.Height * (scaling / 2)),
786 screen.Width /2 + (screen.Width * (scaling / 2)),
787 screen.Height/2 + (screen.Height * (scaling / 2))
790 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
791 partsize.Width, partsize.Height, GL_RGBA,
792 GL_UNSIGNED_BYTE, pixels);
796 // Drop scene manager
799 unsigned int pixelcount = partsize.Width*partsize.Height;
802 for (unsigned int i=0; i < pixelcount; i++) {
820 video::IImage* inventory_image =
821 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
823 rawImage->copyToScaling(inventory_image);
826 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
827 inventory_image->drop();
830 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
834 driver->makeColorKeyTexture(rtt, v2s32(0,0));
836 if(params.delete_texture_on_shutdown)
837 m_texture_trash.push_back(rtt);
843 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
845 static bool warned = false;
848 errorstream<<"TextureSource::generateTextureFromMesh(): "
849 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
855 // Create render target texture
856 video::ITexture *rtt = driver->addRenderTargetTexture(
857 params.dim, params.rtt_texture_name.c_str(),
858 video::ECF_A8R8G8B8);
861 errorstream<<"TextureSource::generateTextureFromMesh(): "
862 <<"addRenderTargetTexture returned NULL."<<std::endl;
867 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
868 driver->removeTexture(rtt);
869 errorstream<<"TextureSource::generateTextureFromMesh(): "
870 <<"failed to set render target"<<std::endl;
874 // Get a scene manager
875 scene::ISceneManager *smgr_main = m_device->getSceneManager();
877 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
880 scene::IMeshSceneNode* meshnode =
881 smgr->addMeshSceneNode(params.mesh, NULL,
882 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
883 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
884 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
885 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
886 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
887 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
889 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
890 params.camera_position, params.camera_lookat);
891 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
892 camera->setProjectionMatrix(params.camera_projection_matrix, false);
894 smgr->setAmbientLight(params.ambient_light);
895 smgr->addLightSceneNode(0,
896 params.light_position,
898 params.light_radius);
901 driver->beginScene(true, true, video::SColor(0,0,0,0));
905 // Drop scene manager
908 // Unset render target
909 driver->setRenderTarget(0, false, true, 0);
911 if(params.delete_texture_on_shutdown)
912 m_texture_trash.push_back(rtt);
917 video::IImage* TextureSource::generateImage(const std::string &name)
923 const char separator = '^';
924 const char paren_open = '(';
925 const char paren_close = ')';
927 // Find last separator in the name
928 s32 last_separator_pos = -1;
930 for(s32 i = name.size() - 1; i >= 0; i--) {
933 if (paren_bal == 0) {
934 last_separator_pos = i;
935 i = -1; // break out of loop
939 if (paren_bal == 0) {
940 errorstream << "generateImage(): unbalanced parentheses"
941 << "(extranous '(') while generating texture \""
942 << name << "\"" << std::endl;
955 errorstream << "generateImage(): unbalanced parentheses"
956 << "(missing matching '(') while generating texture \""
957 << name << "\"" << std::endl;
962 video::IImage *baseimg = NULL;
965 If separator was found, make the base image
966 using a recursive call.
968 if (last_separator_pos != -1) {
969 baseimg = generateImage(name.substr(0, last_separator_pos));
973 video::IVideoDriver* driver = m_device->getVideoDriver();
977 Parse out the last part of the name of the image and act
981 std::string last_part_of_name = name.substr(last_separator_pos + 1);
984 If this name is enclosed in parentheses, generate it
985 and blit it onto the base image
987 if (last_part_of_name[0] == paren_open
988 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
989 std::string name2 = last_part_of_name.substr(1,
990 last_part_of_name.size() - 2);
991 video::IImage *tmp = generateImage(name2);
993 errorstream << "generateImage(): "
994 "Failed to generate \"" << name2 << "\""
998 core::dimension2d<u32> dim = tmp->getDimension();
1000 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1001 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1003 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1004 // Generate image according to part of name
1005 errorstream << "generateImage(): "
1006 "Failed to generate \"" << last_part_of_name << "\""
1010 // If no resulting image, print a warning
1011 if (baseimg == NULL) {
1012 errorstream << "generateImage(): baseimg is NULL (attempted to"
1013 " create texture \"" << name << "\")" << std::endl;
1020 #include <GLES/gl.h>
1022 * Check and align image to npot2 if required by hardware
1023 * @param image image to check for npot2 alignment
1024 * @param driver driver to use for image operations
1025 * @return image or copy of image aligned to npot2
1027 video::IImage * Align2Npot2(video::IImage * image,
1028 video::IVideoDriver* driver)
1034 core::dimension2d<u32> dim = image->getDimension();
1036 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1037 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1041 unsigned int height = npot2(dim.Height);
1042 unsigned int width = npot2(dim.Width);
1044 if ((dim.Height == height) &&
1045 (dim.Width == width)) {
1049 if (dim.Height > height) {
1053 if (dim.Width > width) {
1057 video::IImage *targetimage =
1058 driver->createImage(video::ECF_A8R8G8B8,
1059 core::dimension2d<u32>(width, height));
1061 if (targetimage != NULL) {
1062 image->copyToScaling(targetimage);
1070 bool TextureSource::generateImagePart(std::string part_of_name,
1071 video::IImage *& baseimg)
1073 video::IVideoDriver* driver = m_device->getVideoDriver();
1076 // Stuff starting with [ are special commands
1077 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1079 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1081 image = Align2Npot2(image, driver);
1083 if (image == NULL) {
1084 if (part_of_name != "") {
1085 if (part_of_name.find("_normal.png") == std::string::npos){
1086 errorstream<<"generateImage(): Could not load image \""
1087 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1088 errorstream<<"generateImage(): Creating a dummy"
1089 <<" image for \""<<part_of_name<<"\""<<std::endl;
1091 infostream<<"generateImage(): Could not load normal map \""
1092 <<part_of_name<<"\""<<std::endl;
1093 infostream<<"generateImage(): Creating a dummy"
1094 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1098 // Just create a dummy image
1099 //core::dimension2d<u32> dim(2,2);
1100 core::dimension2d<u32> dim(1,1);
1101 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1103 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1104 image->setPixel(1,0, video::SColor(255,0,255,0));
1105 image->setPixel(0,1, video::SColor(255,0,0,255));
1106 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1107 image->setPixel(0,0, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));
1109 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));
1111 image->setPixel(0,1, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));
1113 image->setPixel(1,1, video::SColor(255,myrand()%256,
1114 myrand()%256,myrand()%256));*/
1117 // If base image is NULL, load as base.
1120 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1122 Copy it this way to get an alpha channel.
1123 Otherwise images with alpha cannot be blitted on
1124 images that don't have alpha in the original file.
1126 core::dimension2d<u32> dim = image->getDimension();
1127 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1128 image->copyTo(baseimg);
1130 // Else blit on base.
1133 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1134 // Size of the copied area
1135 core::dimension2d<u32> dim = image->getDimension();
1136 //core::dimension2d<u32> dim(16,16);
1137 // Position to copy the blitted to in the base image
1138 core::position2d<s32> pos_to(0,0);
1139 // Position to copy the blitted from in the blitted image
1140 core::position2d<s32> pos_from(0,0);
1142 /*image->copyToWithAlpha(baseimg, pos_to,
1143 core::rect<s32>(pos_from, dim),
1144 video::SColor(255,255,255,255),
1146 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1153 // A special texture modification
1155 /*infostream<<"generateImage(): generating special "
1156 <<"modification \""<<part_of_name<<"\""
1162 Adds a cracking texture
1163 N = animation frame count, P = crack progression
1165 if(part_of_name.substr(0,6) == "[crack")
1167 if (baseimg == NULL) {
1168 errorstream<<"generateImagePart(): baseimg == NULL "
1169 <<"for part_of_name=\""<<part_of_name
1170 <<"\", cancelling."<<std::endl;
1174 // Crack image number and overlay option
1175 bool use_overlay = (part_of_name[6] == 'o');
1176 Strfnd sf(part_of_name);
1178 s32 frame_count = stoi(sf.next(":"));
1179 s32 progression = stoi(sf.next(":"));
1184 It is an image with a number of cracking stages
1187 video::IImage *img_crack = m_sourcecache.getOrLoad(
1188 "crack_anylength.png", m_device);
1190 if(img_crack && progression >= 0)
1192 draw_crack(img_crack, baseimg,
1193 use_overlay, frame_count,
1194 progression, driver);
1199 [combine:WxH:X,Y=filename:X,Y=filename2
1200 Creates a bigger texture from an amount of smaller ones
1202 else if(part_of_name.substr(0,8) == "[combine")
1204 Strfnd sf(part_of_name);
1206 u32 w0 = stoi(sf.next("x"));
1207 u32 h0 = stoi(sf.next(":"));
1208 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1209 core::dimension2d<u32> dim(w0,h0);
1210 if (baseimg == NULL) {
1211 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1212 baseimg->fill(video::SColor(0,0,0,0));
1214 while (sf.atend() == false) {
1215 u32 x = stoi(sf.next(","));
1216 u32 y = stoi(sf.next("="));
1217 std::string filename = sf.next(":");
1218 infostream<<"Adding \""<<filename
1219 <<"\" to combined ("<<x<<","<<y<<")"
1221 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1223 core::dimension2d<u32> dim = img->getDimension();
1224 infostream<<"Size "<<dim.Width
1225 <<"x"<<dim.Height<<std::endl;
1226 core::position2d<s32> pos_base(x, y);
1227 video::IImage *img2 =
1228 driver->createImage(video::ECF_A8R8G8B8, dim);
1231 /*img2->copyToWithAlpha(baseimg, pos_base,
1232 core::rect<s32>(v2s32(0,0), dim),
1233 video::SColor(255,255,255,255),
1235 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1238 errorstream << "generateImagePart(): Failed to load image \""
1239 << filename << "\" for [combine" << std::endl;
1246 else if(part_of_name.substr(0,9) == "[brighten")
1248 if (baseimg == NULL) {
1249 errorstream<<"generateImagePart(): baseimg==NULL "
1250 <<"for part_of_name=\""<<part_of_name
1251 <<"\", cancelling."<<std::endl;
1259 Make image completely opaque.
1260 Used for the leaves texture when in old leaves mode, so
1261 that the transparent parts don't look completely black
1262 when simple alpha channel is used for rendering.
1264 else if(part_of_name.substr(0,8) == "[noalpha")
1266 if (baseimg == NULL){
1267 errorstream<<"generateImagePart(): baseimg==NULL "
1268 <<"for part_of_name=\""<<part_of_name
1269 <<"\", cancelling."<<std::endl;
1273 core::dimension2d<u32> dim = baseimg->getDimension();
1275 // Set alpha to full
1276 for(u32 y=0; y<dim.Height; y++)
1277 for(u32 x=0; x<dim.Width; x++)
1279 video::SColor c = baseimg->getPixel(x,y);
1281 baseimg->setPixel(x,y,c);
1286 Convert one color to transparent.
1288 else if(part_of_name.substr(0,11) == "[makealpha:")
1290 if (baseimg == NULL) {
1291 errorstream<<"generateImagePart(): baseimg == NULL "
1292 <<"for part_of_name=\""<<part_of_name
1293 <<"\", cancelling."<<std::endl;
1297 Strfnd sf(part_of_name.substr(11));
1298 u32 r1 = stoi(sf.next(","));
1299 u32 g1 = stoi(sf.next(","));
1300 u32 b1 = stoi(sf.next(""));
1301 std::string filename = sf.next("");
1303 core::dimension2d<u32> dim = baseimg->getDimension();
1305 /*video::IImage *oldbaseimg = baseimg;
1306 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1307 oldbaseimg->copyTo(baseimg);
1308 oldbaseimg->drop();*/
1310 // Set alpha to full
1311 for(u32 y=0; y<dim.Height; y++)
1312 for(u32 x=0; x<dim.Width; x++)
1314 video::SColor c = baseimg->getPixel(x,y);
1316 u32 g = c.getGreen();
1317 u32 b = c.getBlue();
1318 if(!(r == r1 && g == g1 && b == b1))
1321 baseimg->setPixel(x,y,c);
1326 Rotates and/or flips the image.
1328 N can be a number (between 0 and 7) or a transform name.
1329 Rotations are counter-clockwise.
1331 1 R90 rotate by 90 degrees
1332 2 R180 rotate by 180 degrees
1333 3 R270 rotate by 270 degrees
1335 5 FXR90 flip X then rotate by 90 degrees
1337 7 FYR90 flip Y then rotate by 90 degrees
1339 Note: Transform names can be concatenated to produce
1340 their product (applies the first then the second).
1341 The resulting transform will be equivalent to one of the
1342 eight existing ones, though (see: dihedral group).
1344 else if(part_of_name.substr(0,10) == "[transform")
1346 if (baseimg == NULL) {
1347 errorstream<<"generateImagePart(): baseimg == NULL "
1348 <<"for part_of_name=\""<<part_of_name
1349 <<"\", cancelling."<<std::endl;
1353 u32 transform = parseImageTransform(part_of_name.substr(10));
1354 core::dimension2d<u32> dim = imageTransformDimension(
1355 transform, baseimg->getDimension());
1356 video::IImage *image = driver->createImage(
1357 baseimg->getColorFormat(), dim);
1359 imageTransform(transform, baseimg, image);
1364 [inventorycube{topimage{leftimage{rightimage
1365 In every subimage, replace ^ with &.
1366 Create an "inventory cube".
1367 NOTE: This should be used only on its own.
1368 Example (a grass block (not actually used in game):
1369 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1371 else if(part_of_name.substr(0,14) == "[inventorycube")
1373 if (baseimg != NULL){
1374 errorstream<<"generateImagePart(): baseimg != NULL "
1375 <<"for part_of_name=\""<<part_of_name
1376 <<"\", cancelling."<<std::endl;
1380 str_replace_char(part_of_name, '&', '^');
1381 Strfnd sf(part_of_name);
1383 std::string imagename_top = sf.next("{");
1384 std::string imagename_left = sf.next("{");
1385 std::string imagename_right = sf.next("{");
1387 // Generate images for the faces of the cube
1388 video::IImage *img_top = generateImage(imagename_top);
1389 video::IImage *img_left = generateImage(imagename_left);
1390 video::IImage *img_right = generateImage(imagename_right);
1392 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1393 errorstream << "generateImagePart(): Failed to create textures"
1394 << " for inventorycube \"" << part_of_name << "\""
1396 baseimg = generateImage(imagename_top);
1401 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1402 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1404 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1405 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1407 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1408 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1411 // Create textures from images
1412 video::ITexture *texture_top = driver->addTexture(
1413 (imagename_top + "__temp__").c_str(), img_top);
1414 video::ITexture *texture_left = driver->addTexture(
1415 (imagename_left + "__temp__").c_str(), img_left);
1416 video::ITexture *texture_right = driver->addTexture(
1417 (imagename_right + "__temp__").c_str(), img_right);
1418 assert(texture_top && texture_left && texture_right);
1426 Draw a cube mesh into a render target texture
1428 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1429 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1430 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1431 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1432 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1433 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1434 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1435 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1437 TextureFromMeshParams params;
1439 params.dim.set(64, 64);
1440 params.rtt_texture_name = part_of_name + "_RTT";
1441 // We will delete the rtt texture ourselves
1442 params.delete_texture_on_shutdown = false;
1443 params.camera_position.set(0, 1.0, -1.5);
1444 params.camera_position.rotateXZBy(45);
1445 params.camera_lookat.set(0, 0, 0);
1446 // Set orthogonal projection
1447 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1448 1.65, 1.65, 0, 100);
1450 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1451 params.light_position.set(10, 100, -50);
1452 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1453 params.light_radius = 1000;
1455 video::ITexture *rtt = generateTextureFromMesh(params);
1461 driver->removeTexture(texture_top);
1462 driver->removeTexture(texture_left);
1463 driver->removeTexture(texture_right);
1466 baseimg = generateImage(imagename_top);
1470 // Create image of render target
1471 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1475 driver->removeTexture(rtt);
1477 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1480 image->copyTo(baseimg);
1485 [lowpart:percent:filename
1486 Adds the lower part of a texture
1488 else if(part_of_name.substr(0,9) == "[lowpart:")
1490 Strfnd sf(part_of_name);
1492 u32 percent = stoi(sf.next(":"));
1493 std::string filename = sf.next(":");
1494 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1497 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1498 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1501 core::dimension2d<u32> dim = img->getDimension();
1502 core::position2d<s32> pos_base(0, 0);
1503 video::IImage *img2 =
1504 driver->createImage(video::ECF_A8R8G8B8, dim);
1507 core::position2d<s32> clippos(0, 0);
1508 clippos.Y = dim.Height * (100-percent) / 100;
1509 core::dimension2d<u32> clipdim = dim;
1510 clipdim.Height = clipdim.Height * percent / 100 + 1;
1511 core::rect<s32> cliprect(clippos, clipdim);
1512 img2->copyToWithAlpha(baseimg, pos_base,
1513 core::rect<s32>(v2s32(0,0), dim),
1514 video::SColor(255,255,255,255),
1521 Crops a frame of a vertical animation.
1522 N = frame count, I = frame index
1524 else if(part_of_name.substr(0,15) == "[verticalframe:")
1526 Strfnd sf(part_of_name);
1528 u32 frame_count = stoi(sf.next(":"));
1529 u32 frame_index = stoi(sf.next(":"));
1531 if(baseimg == NULL){
1532 errorstream<<"generateImagePart(): baseimg != NULL "
1533 <<"for part_of_name=\""<<part_of_name
1534 <<"\", cancelling."<<std::endl;
1538 v2u32 frame_size = baseimg->getDimension();
1539 frame_size.Y /= frame_count;
1541 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1544 errorstream<<"generateImagePart(): Could not create image "
1545 <<"for part_of_name=\""<<part_of_name
1546 <<"\", cancelling."<<std::endl;
1550 // Fill target image with transparency
1551 img->fill(video::SColor(0,0,0,0));
1553 core::dimension2d<u32> dim = frame_size;
1554 core::position2d<s32> pos_dst(0, 0);
1555 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1556 baseimg->copyToWithAlpha(img, pos_dst,
1557 core::rect<s32>(pos_src, dim),
1558 video::SColor(255,255,255,255),
1566 Applies a mask to an image
1568 else if(part_of_name.substr(0,6) == "[mask:")
1570 if (baseimg == NULL) {
1571 errorstream << "generateImage(): baseimg == NULL "
1572 << "for part_of_name=\"" << part_of_name
1573 << "\", cancelling." << std::endl;
1576 Strfnd sf(part_of_name);
1578 std::string filename = sf.next(":");
1580 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1582 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1583 img->getDimension());
1585 errorstream << "generateImage(): Failed to load \""
1586 << filename << "\".";
1591 errorstream<<"generateImagePart(): Invalid "
1592 " modification: \""<<part_of_name<<"\""<<std::endl;
1600 Draw an image on top of an another one, using the alpha channel of the
1603 This exists because IImage::copyToWithAlpha() doesn't seem to always
1606 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1607 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1609 for(u32 y0=0; y0<size.Y; y0++)
1610 for(u32 x0=0; x0<size.X; x0++)
1612 s32 src_x = src_pos.X + x0;
1613 s32 src_y = src_pos.Y + y0;
1614 s32 dst_x = dst_pos.X + x0;
1615 s32 dst_y = dst_pos.Y + y0;
1616 video::SColor src_c = src->getPixel(src_x, src_y);
1617 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1618 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1619 dst->setPixel(dst_x, dst_y, dst_c);
1624 Draw an image on top of an another one, using the alpha channel of the
1625 source image; only modify fully opaque pixels in destinaion
1627 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1628 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1630 for(u32 y0=0; y0<size.Y; y0++)
1631 for(u32 x0=0; x0<size.X; x0++)
1633 s32 src_x = src_pos.X + x0;
1634 s32 src_y = src_pos.Y + y0;
1635 s32 dst_x = dst_pos.X + x0;
1636 s32 dst_y = dst_pos.Y + y0;
1637 video::SColor src_c = src->getPixel(src_x, src_y);
1638 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1639 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1641 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1642 dst->setPixel(dst_x, dst_y, dst_c);
1648 Apply mask to destination
1650 static void apply_mask(video::IImage *mask, video::IImage *dst,
1651 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1653 for(u32 y0 = 0; y0 < size.Y; y0++) {
1654 for(u32 x0 = 0; x0 < size.X; x0++) {
1655 s32 mask_x = x0 + mask_pos.X;
1656 s32 mask_y = y0 + mask_pos.Y;
1657 s32 dst_x = x0 + dst_pos.X;
1658 s32 dst_y = y0 + dst_pos.Y;
1659 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1660 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1661 dst_c.color &= mask_c.color;
1662 dst->setPixel(dst_x, dst_y, dst_c);
1667 static void draw_crack(video::IImage *crack, video::IImage *dst,
1668 bool use_overlay, s32 frame_count, s32 progression,
1669 video::IVideoDriver *driver)
1671 // Dimension of destination image
1672 core::dimension2d<u32> dim_dst = dst->getDimension();
1673 // Dimension of original image
1674 core::dimension2d<u32> dim_crack = crack->getDimension();
1675 // Count of crack stages
1676 s32 crack_count = dim_crack.Height / dim_crack.Width;
1677 // Limit frame_count
1678 if(frame_count > (s32) dim_dst.Height)
1679 frame_count = dim_dst.Height;
1682 // Limit progression
1683 if(progression > crack_count-1)
1684 progression = crack_count-1;
1685 // Dimension of a single crack stage
1686 core::dimension2d<u32> dim_crack_cropped(
1690 // Dimension of the scaled crack stage,
1691 // which is the same as the dimension of a single destination frame
1692 core::dimension2d<u32> dim_crack_scaled(
1694 dim_dst.Height / frame_count
1696 // Create cropped and scaled crack images
1697 video::IImage *crack_cropped = driver->createImage(
1698 video::ECF_A8R8G8B8, dim_crack_cropped);
1699 video::IImage *crack_scaled = driver->createImage(
1700 video::ECF_A8R8G8B8, dim_crack_scaled);
1702 if(crack_cropped && crack_scaled)
1705 v2s32 pos_crack(0, progression*dim_crack.Width);
1706 crack->copyTo(crack_cropped,
1708 core::rect<s32>(pos_crack, dim_crack_cropped));
1709 // Scale crack image by copying
1710 crack_cropped->copyToScaling(crack_scaled);
1711 // Copy or overlay crack image onto each frame
1712 for(s32 i = 0; i < frame_count; ++i)
1714 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1717 blit_with_alpha_overlay(crack_scaled, dst,
1718 v2s32(0,0), dst_pos,
1723 blit_with_alpha(crack_scaled, dst,
1724 v2s32(0,0), dst_pos,
1731 crack_scaled->drop();
1734 crack_cropped->drop();
1737 void brighten(video::IImage *image)
1742 core::dimension2d<u32> dim = image->getDimension();
1744 for(u32 y=0; y<dim.Height; y++)
1745 for(u32 x=0; x<dim.Width; x++)
1747 video::SColor c = image->getPixel(x,y);
1748 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1749 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1750 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1751 image->setPixel(x,y,c);
1755 u32 parseImageTransform(const std::string& s)
1757 int total_transform = 0;
1759 std::string transform_names[8];
1760 transform_names[0] = "i";
1761 transform_names[1] = "r90";
1762 transform_names[2] = "r180";
1763 transform_names[3] = "r270";
1764 transform_names[4] = "fx";
1765 transform_names[6] = "fy";
1767 std::size_t pos = 0;
1768 while(pos < s.size())
1771 for(int i = 0; i <= 7; ++i)
1773 const std::string &name_i = transform_names[i];
1775 if(s[pos] == ('0' + i))
1781 else if(!(name_i.empty()) &&
1782 lowercase(s.substr(pos, name_i.size())) == name_i)
1785 pos += name_i.size();
1792 // Multiply total_transform and transform in the group D4
1795 new_total = (transform + total_transform) % 4;
1797 new_total = (transform - total_transform + 8) % 4;
1798 if((transform >= 4) ^ (total_transform >= 4))
1801 total_transform = new_total;
1803 return total_transform;
1806 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1808 if(transform % 2 == 0)
1811 return core::dimension2d<u32>(dim.Height, dim.Width);
1814 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1816 if(src == NULL || dst == NULL)
1819 core::dimension2d<u32> srcdim = src->getDimension();
1820 core::dimension2d<u32> dstdim = dst->getDimension();
1822 assert(dstdim == imageTransformDimension(transform, srcdim));
1823 assert(transform >= 0 && transform <= 7);
1826 Compute the transformation from source coordinates (sx,sy)
1827 to destination coordinates (dx,dy).
1831 if(transform == 0) // identity
1832 sxn = 0, syn = 2; // sx = dx, sy = dy
1833 else if(transform == 1) // rotate by 90 degrees ccw
1834 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1835 else if(transform == 2) // rotate by 180 degrees
1836 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1837 else if(transform == 3) // rotate by 270 degrees ccw
1838 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1839 else if(transform == 4) // flip x
1840 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1841 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1842 sxn = 2, syn = 0; // sx = dy, sy = dx
1843 else if(transform == 6) // flip y
1844 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1845 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1846 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1848 for(u32 dy=0; dy<dstdim.Height; dy++)
1849 for(u32 dx=0; dx<dstdim.Width; dx++)
1851 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1852 u32 sx = entries[sxn];
1853 u32 sy = entries[syn];
1854 video::SColor c = src->getPixel(sx,sy);
1855 dst->setPixel(dx,dy,c);
1859 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1862 if (isKnownSourceImage("override_normal.png"))
1863 return getTexture("override_normal.png", &id);
1864 std::string fname_base = name;
1865 std::string normal_ext = "_normal.png";
1866 size_t pos = fname_base.find(".");
1867 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1868 if (isKnownSourceImage(fname_normal)) {
1869 // look for image extension and replace it
1871 while ((i = fname_base.find(".", i)) != std::string::npos) {
1872 fname_base.replace(i, 4, normal_ext);
1873 i += normal_ext.length();
1875 return getTexture(fname_base, &id);