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 // Draw or overlay a crack
541 static void draw_crack(video::IImage *crack, video::IImage *dst,
542 bool use_overlay, s32 frame_count, s32 progression,
543 video::IVideoDriver *driver);
546 void brighten(video::IImage *image);
547 // Parse a transform name
548 u32 parseImageTransform(const std::string& s);
549 // Apply transform to image dimension
550 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
551 // Apply transform to image data
552 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
555 This method generates all the textures
557 u32 TextureSource::generateTexture(const std::string &name)
559 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
561 // Empty name means texture 0
563 infostream<<"generateTexture(): name is empty"<<std::endl;
569 See if texture already exists
571 JMutexAutoLock lock(m_textureinfo_cache_mutex);
572 std::map<std::string, u32>::iterator n;
573 n = m_name_to_id.find(name);
574 if (n != m_name_to_id.end()) {
580 Calling only allowed from main thread
582 if (get_current_thread_id() != m_main_thread) {
583 errorstream<<"TextureSource::generateTexture() "
584 "called not from main thread"<<std::endl;
588 video::IVideoDriver *driver = m_device->getVideoDriver();
591 video::IImage *img = generateImage(name);
593 video::ITexture *tex = NULL;
597 img = Align2Npot2(img, driver);
599 // Create texture from resulting image
600 tex = driver->addTexture(name.c_str(), img);
605 Add texture to caches (add NULL textures too)
608 JMutexAutoLock lock(m_textureinfo_cache_mutex);
610 u32 id = m_textureinfo_cache.size();
611 TextureInfo ti(name, tex);
612 m_textureinfo_cache.push_back(ti);
613 m_name_to_id[name] = id;
618 std::string TextureSource::getTextureName(u32 id)
620 JMutexAutoLock lock(m_textureinfo_cache_mutex);
622 if(id >= m_textureinfo_cache.size())
624 errorstream<<"TextureSource::getTextureName(): id="<<id
625 <<" >= m_textureinfo_cache.size()="
626 <<m_textureinfo_cache.size()<<std::endl;
630 return m_textureinfo_cache[id].name;
633 video::ITexture* TextureSource::getTexture(u32 id)
635 JMutexAutoLock lock(m_textureinfo_cache_mutex);
637 if(id >= m_textureinfo_cache.size())
640 return m_textureinfo_cache[id].texture;
643 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
645 u32 actual_id = getTextureId(name);
649 return getTexture(actual_id);
652 void TextureSource::processQueue()
657 //NOTE this is only thread safe for ONE consumer thread!
658 if(!m_get_texture_queue.empty())
660 GetRequest<std::string, u32, u8, u8>
661 request = m_get_texture_queue.pop();
663 /*infostream<<"TextureSource::processQueue(): "
664 <<"got texture request with "
665 <<"name=\""<<request.key<<"\""
668 m_get_texture_queue.pushResult(request, generateTexture(request.key));
672 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
674 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
676 assert(get_current_thread_id() == m_main_thread);
678 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
679 m_source_image_existence.set(name, true);
682 void TextureSource::rebuildImagesAndTextures()
684 JMutexAutoLock lock(m_textureinfo_cache_mutex);
686 video::IVideoDriver* driver = m_device->getVideoDriver();
690 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
691 TextureInfo *ti = &m_textureinfo_cache[i];
692 video::IImage *img = generateImage(ti->name);
694 img = Align2Npot2(img, driver);
695 assert(img->getDimension().Height == npot2(img->getDimension().Height));
696 assert(img->getDimension().Width == npot2(img->getDimension().Width));
698 // Create texture from resulting image
699 video::ITexture *t = NULL;
701 t = driver->addTexture(ti->name.c_str(), img);
704 video::ITexture *t_old = ti->texture;
709 m_texture_trash.push_back(t_old);
713 video::ITexture* TextureSource::generateTextureFromMesh(
714 const TextureFromMeshParams ¶ms)
716 video::IVideoDriver *driver = m_device->getVideoDriver();
720 const GLubyte* renderstr = glGetString(GL_RENDERER);
721 std::string renderer((char*) renderstr);
723 // use no render to texture hack
725 (renderer.find("Adreno") != std::string::npos) ||
726 (renderer.find("Mali") != std::string::npos) ||
727 (renderer.find("Immersion") != std::string::npos) ||
728 (renderer.find("Tegra") != std::string::npos) ||
729 g_settings->getBool("inventory_image_hack")
731 // Get a scene manager
732 scene::ISceneManager *smgr_main = m_device->getSceneManager();
734 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
737 const float scaling = 0.2;
739 scene::IMeshSceneNode* meshnode =
740 smgr->addMeshSceneNode(params.mesh, NULL,
741 -1, v3f(0,0,0), v3f(0,0,0),
742 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
743 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
744 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
745 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
746 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
747 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
749 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
750 params.camera_position, params.camera_lookat);
751 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
752 camera->setProjectionMatrix(params.camera_projection_matrix, false);
754 smgr->setAmbientLight(params.ambient_light);
755 smgr->addLightSceneNode(0,
756 params.light_position,
758 params.light_radius*scaling);
760 core::dimension2d<u32> screen = driver->getScreenSize();
763 driver->beginScene(true, true, video::SColor(0,0,0,0));
764 driver->clearZBuffer();
767 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
769 irr::video::IImage* rawImage =
770 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
772 u8* pixels = static_cast<u8*>(rawImage->lock());
779 core::rect<s32> source(
780 screen.Width /2 - (screen.Width * (scaling / 2)),
781 screen.Height/2 - (screen.Height * (scaling / 2)),
782 screen.Width /2 + (screen.Width * (scaling / 2)),
783 screen.Height/2 + (screen.Height * (scaling / 2))
786 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
787 partsize.Width, partsize.Height, GL_RGBA,
788 GL_UNSIGNED_BYTE, pixels);
792 // Drop scene manager
795 unsigned int pixelcount = partsize.Width*partsize.Height;
798 for (unsigned int i=0; i < pixelcount; i++) {
816 video::IImage* inventory_image =
817 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
819 rawImage->copyToScaling(inventory_image);
822 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
823 inventory_image->drop();
826 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
830 driver->makeColorKeyTexture(rtt, v2s32(0,0));
832 if(params.delete_texture_on_shutdown)
833 m_texture_trash.push_back(rtt);
839 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
841 static bool warned = false;
844 errorstream<<"TextureSource::generateTextureFromMesh(): "
845 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
851 // Create render target texture
852 video::ITexture *rtt = driver->addRenderTargetTexture(
853 params.dim, params.rtt_texture_name.c_str(),
854 video::ECF_A8R8G8B8);
857 errorstream<<"TextureSource::generateTextureFromMesh(): "
858 <<"addRenderTargetTexture returned NULL."<<std::endl;
863 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
864 driver->removeTexture(rtt);
865 errorstream<<"TextureSource::generateTextureFromMesh(): "
866 <<"failed to set render target"<<std::endl;
870 // Get a scene manager
871 scene::ISceneManager *smgr_main = m_device->getSceneManager();
873 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
876 scene::IMeshSceneNode* meshnode =
877 smgr->addMeshSceneNode(params.mesh, NULL,
878 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
879 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
880 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
881 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
882 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
883 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
885 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
886 params.camera_position, params.camera_lookat);
887 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
888 camera->setProjectionMatrix(params.camera_projection_matrix, false);
890 smgr->setAmbientLight(params.ambient_light);
891 smgr->addLightSceneNode(0,
892 params.light_position,
894 params.light_radius);
897 driver->beginScene(true, true, video::SColor(0,0,0,0));
901 // Drop scene manager
904 // Unset render target
905 driver->setRenderTarget(0, false, true, 0);
907 if(params.delete_texture_on_shutdown)
908 m_texture_trash.push_back(rtt);
913 video::IImage* TextureSource::generateImage(const std::string &name)
919 const char separator = '^';
920 const char paren_open = '(';
921 const char paren_close = ')';
923 // Find last separator in the name
924 s32 last_separator_pos = -1;
926 for(s32 i = name.size() - 1; i >= 0; i--) {
929 if (paren_bal == 0) {
930 last_separator_pos = i;
931 i = -1; // break out of loop
935 if (paren_bal == 0) {
936 errorstream << "generateImage(): unbalanced parentheses"
937 << "(extranous '(') while generating texture \""
938 << name << "\"" << std::endl;
951 errorstream << "generateImage(): unbalanced parentheses"
952 << "(missing matching '(') while generating texture \""
953 << name << "\"" << std::endl;
958 video::IImage *baseimg = NULL;
961 If separator was found, make the base image
962 using a recursive call.
964 if (last_separator_pos != -1) {
965 baseimg = generateImage(name.substr(0, last_separator_pos));
969 video::IVideoDriver* driver = m_device->getVideoDriver();
973 Parse out the last part of the name of the image and act
977 std::string last_part_of_name = name.substr(last_separator_pos + 1);
980 If this name is enclosed in parentheses, generate it
981 and blit it onto the base image
983 if (last_part_of_name[0] == paren_open
984 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
985 std::string name2 = last_part_of_name.substr(1,
986 last_part_of_name.size() - 2);
987 video::IImage *tmp = generateImage(name2);
989 errorstream << "generateImage(): "
990 "Failed to generate \"" << name2 << "\""
994 core::dimension2d<u32> dim = tmp->getDimension();
996 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
997 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
999 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1000 // Generate image according to part of name
1001 errorstream << "generateImage(): "
1002 "Failed to generate \"" << last_part_of_name << "\""
1006 // If no resulting image, print a warning
1007 if (baseimg == NULL) {
1008 errorstream << "generateImage(): baseimg is NULL (attempted to"
1009 " create texture \"" << name << "\")" << std::endl;
1016 #include <GLES/gl.h>
1018 * Check and align image to npot2 if required by hardware
1019 * @param image image to check for npot2 alignment
1020 * @param driver driver to use for image operations
1021 * @return image or copy of image aligned to npot2
1023 video::IImage * Align2Npot2(video::IImage * image,
1024 video::IVideoDriver* driver)
1030 core::dimension2d<u32> dim = image->getDimension();
1032 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1033 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1037 unsigned int height = npot2(dim.Height);
1038 unsigned int width = npot2(dim.Width);
1040 if ((dim.Height == height) &&
1041 (dim.Width == width)) {
1045 if (dim.Height > height) {
1049 if (dim.Width > width) {
1053 video::IImage *targetimage =
1054 driver->createImage(video::ECF_A8R8G8B8,
1055 core::dimension2d<u32>(width, height));
1057 if (targetimage != NULL) {
1058 image->copyToScaling(targetimage);
1066 bool TextureSource::generateImagePart(std::string part_of_name,
1067 video::IImage *& baseimg)
1069 video::IVideoDriver* driver = m_device->getVideoDriver();
1072 // Stuff starting with [ are special commands
1073 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1075 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1077 image = Align2Npot2(image, driver);
1079 if (image == NULL) {
1080 if (part_of_name != "") {
1081 if (part_of_name.find("_normal.png") == std::string::npos){
1082 errorstream<<"generateImage(): Could not load image \""
1083 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1084 errorstream<<"generateImage(): Creating a dummy"
1085 <<" image for \""<<part_of_name<<"\""<<std::endl;
1087 infostream<<"generateImage(): Could not load normal map \""
1088 <<part_of_name<<"\""<<std::endl;
1089 infostream<<"generateImage(): Creating a dummy"
1090 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1094 // Just create a dummy image
1095 //core::dimension2d<u32> dim(2,2);
1096 core::dimension2d<u32> dim(1,1);
1097 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1099 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1100 image->setPixel(1,0, video::SColor(255,0,255,0));
1101 image->setPixel(0,1, video::SColor(255,0,0,255));
1102 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1103 image->setPixel(0,0, video::SColor(255,myrand()%256,
1104 myrand()%256,myrand()%256));
1105 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1106 myrand()%256,myrand()%256));
1107 image->setPixel(0,1, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));
1109 image->setPixel(1,1, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));*/
1113 // If base image is NULL, load as base.
1116 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1118 Copy it this way to get an alpha channel.
1119 Otherwise images with alpha cannot be blitted on
1120 images that don't have alpha in the original file.
1122 core::dimension2d<u32> dim = image->getDimension();
1123 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1124 image->copyTo(baseimg);
1126 // Else blit on base.
1129 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1130 // Size of the copied area
1131 core::dimension2d<u32> dim = image->getDimension();
1132 //core::dimension2d<u32> dim(16,16);
1133 // Position to copy the blitted to in the base image
1134 core::position2d<s32> pos_to(0,0);
1135 // Position to copy the blitted from in the blitted image
1136 core::position2d<s32> pos_from(0,0);
1138 /*image->copyToWithAlpha(baseimg, pos_to,
1139 core::rect<s32>(pos_from, dim),
1140 video::SColor(255,255,255,255),
1142 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1149 // A special texture modification
1151 /*infostream<<"generateImage(): generating special "
1152 <<"modification \""<<part_of_name<<"\""
1158 Adds a cracking texture
1159 N = animation frame count, P = crack progression
1161 if(part_of_name.substr(0,6) == "[crack")
1163 if (baseimg == NULL) {
1164 errorstream<<"generateImagePart(): baseimg == NULL "
1165 <<"for part_of_name=\""<<part_of_name
1166 <<"\", cancelling."<<std::endl;
1170 // Crack image number and overlay option
1171 bool use_overlay = (part_of_name[6] == 'o');
1172 Strfnd sf(part_of_name);
1174 s32 frame_count = stoi(sf.next(":"));
1175 s32 progression = stoi(sf.next(":"));
1180 It is an image with a number of cracking stages
1183 video::IImage *img_crack = m_sourcecache.getOrLoad(
1184 "crack_anylength.png", m_device);
1186 if(img_crack && progression >= 0)
1188 draw_crack(img_crack, baseimg,
1189 use_overlay, frame_count,
1190 progression, driver);
1195 [combine:WxH:X,Y=filename:X,Y=filename2
1196 Creates a bigger texture from an amount of smaller ones
1198 else if(part_of_name.substr(0,8) == "[combine")
1200 Strfnd sf(part_of_name);
1202 u32 w0 = stoi(sf.next("x"));
1203 u32 h0 = stoi(sf.next(":"));
1204 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1205 core::dimension2d<u32> dim(w0,h0);
1206 if (baseimg == NULL) {
1207 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1208 baseimg->fill(video::SColor(0,0,0,0));
1210 while (sf.atend() == false) {
1211 u32 x = stoi(sf.next(","));
1212 u32 y = stoi(sf.next("="));
1213 std::string filename = sf.next(":");
1214 infostream<<"Adding \""<<filename
1215 <<"\" to combined ("<<x<<","<<y<<")"
1217 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1219 core::dimension2d<u32> dim = img->getDimension();
1220 infostream<<"Size "<<dim.Width
1221 <<"x"<<dim.Height<<std::endl;
1222 core::position2d<s32> pos_base(x, y);
1223 video::IImage *img2 =
1224 driver->createImage(video::ECF_A8R8G8B8, dim);
1227 /*img2->copyToWithAlpha(baseimg, pos_base,
1228 core::rect<s32>(v2s32(0,0), dim),
1229 video::SColor(255,255,255,255),
1231 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1234 errorstream << "generateImagePart(): Failed to load image \""
1235 << filename << "\" for [combine" << std::endl;
1242 else if(part_of_name.substr(0,9) == "[brighten")
1244 if (baseimg == NULL) {
1245 errorstream<<"generateImagePart(): baseimg==NULL "
1246 <<"for part_of_name=\""<<part_of_name
1247 <<"\", cancelling."<<std::endl;
1255 Make image completely opaque.
1256 Used for the leaves texture when in old leaves mode, so
1257 that the transparent parts don't look completely black
1258 when simple alpha channel is used for rendering.
1260 else if(part_of_name.substr(0,8) == "[noalpha")
1262 if (baseimg == NULL){
1263 errorstream<<"generateImagePart(): baseimg==NULL "
1264 <<"for part_of_name=\""<<part_of_name
1265 <<"\", cancelling."<<std::endl;
1269 core::dimension2d<u32> dim = baseimg->getDimension();
1271 // Set alpha to full
1272 for(u32 y=0; y<dim.Height; y++)
1273 for(u32 x=0; x<dim.Width; x++)
1275 video::SColor c = baseimg->getPixel(x,y);
1277 baseimg->setPixel(x,y,c);
1282 Convert one color to transparent.
1284 else if(part_of_name.substr(0,11) == "[makealpha:")
1286 if (baseimg == NULL) {
1287 errorstream<<"generateImagePart(): baseimg == NULL "
1288 <<"for part_of_name=\""<<part_of_name
1289 <<"\", cancelling."<<std::endl;
1293 Strfnd sf(part_of_name.substr(11));
1294 u32 r1 = stoi(sf.next(","));
1295 u32 g1 = stoi(sf.next(","));
1296 u32 b1 = stoi(sf.next(""));
1297 std::string filename = sf.next("");
1299 core::dimension2d<u32> dim = baseimg->getDimension();
1301 /*video::IImage *oldbaseimg = baseimg;
1302 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1303 oldbaseimg->copyTo(baseimg);
1304 oldbaseimg->drop();*/
1306 // Set alpha to full
1307 for(u32 y=0; y<dim.Height; y++)
1308 for(u32 x=0; x<dim.Width; x++)
1310 video::SColor c = baseimg->getPixel(x,y);
1312 u32 g = c.getGreen();
1313 u32 b = c.getBlue();
1314 if(!(r == r1 && g == g1 && b == b1))
1317 baseimg->setPixel(x,y,c);
1322 Rotates and/or flips the image.
1324 N can be a number (between 0 and 7) or a transform name.
1325 Rotations are counter-clockwise.
1327 1 R90 rotate by 90 degrees
1328 2 R180 rotate by 180 degrees
1329 3 R270 rotate by 270 degrees
1331 5 FXR90 flip X then rotate by 90 degrees
1333 7 FYR90 flip Y then rotate by 90 degrees
1335 Note: Transform names can be concatenated to produce
1336 their product (applies the first then the second).
1337 The resulting transform will be equivalent to one of the
1338 eight existing ones, though (see: dihedral group).
1340 else if(part_of_name.substr(0,10) == "[transform")
1342 if (baseimg == NULL) {
1343 errorstream<<"generateImagePart(): baseimg == NULL "
1344 <<"for part_of_name=\""<<part_of_name
1345 <<"\", cancelling."<<std::endl;
1349 u32 transform = parseImageTransform(part_of_name.substr(10));
1350 core::dimension2d<u32> dim = imageTransformDimension(
1351 transform, baseimg->getDimension());
1352 video::IImage *image = driver->createImage(
1353 baseimg->getColorFormat(), dim);
1355 imageTransform(transform, baseimg, image);
1360 [inventorycube{topimage{leftimage{rightimage
1361 In every subimage, replace ^ with &.
1362 Create an "inventory cube".
1363 NOTE: This should be used only on its own.
1364 Example (a grass block (not actually used in game):
1365 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1367 else if(part_of_name.substr(0,14) == "[inventorycube")
1369 if (baseimg != NULL){
1370 errorstream<<"generateImagePart(): baseimg != NULL "
1371 <<"for part_of_name=\""<<part_of_name
1372 <<"\", cancelling."<<std::endl;
1376 str_replace_char(part_of_name, '&', '^');
1377 Strfnd sf(part_of_name);
1379 std::string imagename_top = sf.next("{");
1380 std::string imagename_left = sf.next("{");
1381 std::string imagename_right = sf.next("{");
1383 // Generate images for the faces of the cube
1384 video::IImage *img_top = generateImage(imagename_top);
1385 video::IImage *img_left = generateImage(imagename_left);
1386 video::IImage *img_right = generateImage(imagename_right);
1388 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1389 errorstream << "generateImagePart(): Failed to create textures"
1390 << " for inventorycube \"" << part_of_name << "\""
1392 baseimg = generateImage(imagename_top);
1397 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1398 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1400 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1401 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1403 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1404 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1407 // Create textures from images
1408 video::ITexture *texture_top = driver->addTexture(
1409 (imagename_top + "__temp__").c_str(), img_top);
1410 video::ITexture *texture_left = driver->addTexture(
1411 (imagename_left + "__temp__").c_str(), img_left);
1412 video::ITexture *texture_right = driver->addTexture(
1413 (imagename_right + "__temp__").c_str(), img_right);
1414 assert(texture_top && texture_left && texture_right);
1422 Draw a cube mesh into a render target texture
1424 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1425 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1426 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1427 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1428 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1429 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1430 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1431 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1433 TextureFromMeshParams params;
1435 params.dim.set(64, 64);
1436 params.rtt_texture_name = part_of_name + "_RTT";
1437 // We will delete the rtt texture ourselves
1438 params.delete_texture_on_shutdown = false;
1439 params.camera_position.set(0, 1.0, -1.5);
1440 params.camera_position.rotateXZBy(45);
1441 params.camera_lookat.set(0, 0, 0);
1442 // Set orthogonal projection
1443 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1444 1.65, 1.65, 0, 100);
1446 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1447 params.light_position.set(10, 100, -50);
1448 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1449 params.light_radius = 1000;
1451 video::ITexture *rtt = generateTextureFromMesh(params);
1457 driver->removeTexture(texture_top);
1458 driver->removeTexture(texture_left);
1459 driver->removeTexture(texture_right);
1462 baseimg = generateImage(imagename_top);
1466 // Create image of render target
1467 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1471 driver->removeTexture(rtt);
1473 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1476 image->copyTo(baseimg);
1481 [lowpart:percent:filename
1482 Adds the lower part of a texture
1484 else if(part_of_name.substr(0,9) == "[lowpart:")
1486 Strfnd sf(part_of_name);
1488 u32 percent = stoi(sf.next(":"));
1489 std::string filename = sf.next(":");
1490 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1493 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1494 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1497 core::dimension2d<u32> dim = img->getDimension();
1498 core::position2d<s32> pos_base(0, 0);
1499 video::IImage *img2 =
1500 driver->createImage(video::ECF_A8R8G8B8, dim);
1503 core::position2d<s32> clippos(0, 0);
1504 clippos.Y = dim.Height * (100-percent) / 100;
1505 core::dimension2d<u32> clipdim = dim;
1506 clipdim.Height = clipdim.Height * percent / 100 + 1;
1507 core::rect<s32> cliprect(clippos, clipdim);
1508 img2->copyToWithAlpha(baseimg, pos_base,
1509 core::rect<s32>(v2s32(0,0), dim),
1510 video::SColor(255,255,255,255),
1517 Crops a frame of a vertical animation.
1518 N = frame count, I = frame index
1520 else if(part_of_name.substr(0,15) == "[verticalframe:")
1522 Strfnd sf(part_of_name);
1524 u32 frame_count = stoi(sf.next(":"));
1525 u32 frame_index = stoi(sf.next(":"));
1527 if(baseimg == NULL){
1528 errorstream<<"generateImagePart(): baseimg != NULL "
1529 <<"for part_of_name=\""<<part_of_name
1530 <<"\", cancelling."<<std::endl;
1534 v2u32 frame_size = baseimg->getDimension();
1535 frame_size.Y /= frame_count;
1537 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1540 errorstream<<"generateImagePart(): Could not create image "
1541 <<"for part_of_name=\""<<part_of_name
1542 <<"\", cancelling."<<std::endl;
1546 // Fill target image with transparency
1547 img->fill(video::SColor(0,0,0,0));
1549 core::dimension2d<u32> dim = frame_size;
1550 core::position2d<s32> pos_dst(0, 0);
1551 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1552 baseimg->copyToWithAlpha(img, pos_dst,
1553 core::rect<s32>(pos_src, dim),
1554 video::SColor(255,255,255,255),
1562 errorstream<<"generateImagePart(): Invalid "
1563 " modification: \""<<part_of_name<<"\""<<std::endl;
1571 Draw an image on top of an another one, using the alpha channel of the
1574 This exists because IImage::copyToWithAlpha() doesn't seem to always
1577 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1578 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1580 for(u32 y0=0; y0<size.Y; y0++)
1581 for(u32 x0=0; x0<size.X; x0++)
1583 s32 src_x = src_pos.X + x0;
1584 s32 src_y = src_pos.Y + y0;
1585 s32 dst_x = dst_pos.X + x0;
1586 s32 dst_y = dst_pos.Y + y0;
1587 video::SColor src_c = src->getPixel(src_x, src_y);
1588 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1589 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1590 dst->setPixel(dst_x, dst_y, dst_c);
1595 Draw an image on top of an another one, using the alpha channel of the
1596 source image; only modify fully opaque pixels in destinaion
1598 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1599 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1601 for(u32 y0=0; y0<size.Y; y0++)
1602 for(u32 x0=0; x0<size.X; x0++)
1604 s32 src_x = src_pos.X + x0;
1605 s32 src_y = src_pos.Y + y0;
1606 s32 dst_x = dst_pos.X + x0;
1607 s32 dst_y = dst_pos.Y + y0;
1608 video::SColor src_c = src->getPixel(src_x, src_y);
1609 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1610 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1612 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1613 dst->setPixel(dst_x, dst_y, dst_c);
1618 static void draw_crack(video::IImage *crack, video::IImage *dst,
1619 bool use_overlay, s32 frame_count, s32 progression,
1620 video::IVideoDriver *driver)
1622 // Dimension of destination image
1623 core::dimension2d<u32> dim_dst = dst->getDimension();
1624 // Dimension of original image
1625 core::dimension2d<u32> dim_crack = crack->getDimension();
1626 // Count of crack stages
1627 s32 crack_count = dim_crack.Height / dim_crack.Width;
1628 // Limit frame_count
1629 if(frame_count > (s32) dim_dst.Height)
1630 frame_count = dim_dst.Height;
1633 // Limit progression
1634 if(progression > crack_count-1)
1635 progression = crack_count-1;
1636 // Dimension of a single crack stage
1637 core::dimension2d<u32> dim_crack_cropped(
1641 // Dimension of the scaled crack stage,
1642 // which is the same as the dimension of a single destination frame
1643 core::dimension2d<u32> dim_crack_scaled(
1645 dim_dst.Height / frame_count
1647 // Create cropped and scaled crack images
1648 video::IImage *crack_cropped = driver->createImage(
1649 video::ECF_A8R8G8B8, dim_crack_cropped);
1650 video::IImage *crack_scaled = driver->createImage(
1651 video::ECF_A8R8G8B8, dim_crack_scaled);
1653 if(crack_cropped && crack_scaled)
1656 v2s32 pos_crack(0, progression*dim_crack.Width);
1657 crack->copyTo(crack_cropped,
1659 core::rect<s32>(pos_crack, dim_crack_cropped));
1660 // Scale crack image by copying
1661 crack_cropped->copyToScaling(crack_scaled);
1662 // Copy or overlay crack image onto each frame
1663 for(s32 i = 0; i < frame_count; ++i)
1665 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1668 blit_with_alpha_overlay(crack_scaled, dst,
1669 v2s32(0,0), dst_pos,
1674 blit_with_alpha(crack_scaled, dst,
1675 v2s32(0,0), dst_pos,
1682 crack_scaled->drop();
1685 crack_cropped->drop();
1688 void brighten(video::IImage *image)
1693 core::dimension2d<u32> dim = image->getDimension();
1695 for(u32 y=0; y<dim.Height; y++)
1696 for(u32 x=0; x<dim.Width; x++)
1698 video::SColor c = image->getPixel(x,y);
1699 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1700 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1701 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1702 image->setPixel(x,y,c);
1706 u32 parseImageTransform(const std::string& s)
1708 int total_transform = 0;
1710 std::string transform_names[8];
1711 transform_names[0] = "i";
1712 transform_names[1] = "r90";
1713 transform_names[2] = "r180";
1714 transform_names[3] = "r270";
1715 transform_names[4] = "fx";
1716 transform_names[6] = "fy";
1718 std::size_t pos = 0;
1719 while(pos < s.size())
1722 for(int i = 0; i <= 7; ++i)
1724 const std::string &name_i = transform_names[i];
1726 if(s[pos] == ('0' + i))
1732 else if(!(name_i.empty()) &&
1733 lowercase(s.substr(pos, name_i.size())) == name_i)
1736 pos += name_i.size();
1743 // Multiply total_transform and transform in the group D4
1746 new_total = (transform + total_transform) % 4;
1748 new_total = (transform - total_transform + 8) % 4;
1749 if((transform >= 4) ^ (total_transform >= 4))
1752 total_transform = new_total;
1754 return total_transform;
1757 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1759 if(transform % 2 == 0)
1762 return core::dimension2d<u32>(dim.Height, dim.Width);
1765 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1767 if(src == NULL || dst == NULL)
1770 core::dimension2d<u32> srcdim = src->getDimension();
1771 core::dimension2d<u32> dstdim = dst->getDimension();
1773 assert(dstdim == imageTransformDimension(transform, srcdim));
1774 assert(transform >= 0 && transform <= 7);
1777 Compute the transformation from source coordinates (sx,sy)
1778 to destination coordinates (dx,dy).
1782 if(transform == 0) // identity
1783 sxn = 0, syn = 2; // sx = dx, sy = dy
1784 else if(transform == 1) // rotate by 90 degrees ccw
1785 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1786 else if(transform == 2) // rotate by 180 degrees
1787 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1788 else if(transform == 3) // rotate by 270 degrees ccw
1789 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1790 else if(transform == 4) // flip x
1791 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1792 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1793 sxn = 2, syn = 0; // sx = dy, sy = dx
1794 else if(transform == 6) // flip y
1795 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1796 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1797 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1799 for(u32 dy=0; dy<dstdim.Height; dy++)
1800 for(u32 dx=0; dx<dstdim.Width; dx++)
1802 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1803 u32 sx = entries[sxn];
1804 u32 sy = entries[syn];
1805 video::SColor c = src->getPixel(sx,sy);
1806 dst->setPixel(dx,dy,c);
1810 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1813 if (isKnownSourceImage("override_normal.png"))
1814 return getTexture("override_normal.png", &id);
1815 std::string fname_base = name;
1816 std::string normal_ext = "_normal.png";
1817 size_t pos = fname_base.find(".");
1818 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1819 if (isKnownSourceImage(fname_normal)) {
1820 // look for image extension and replace it
1822 while ((i = fname_base.find(".", i)) != std::string::npos) {
1823 fname_base.replace(i, 4, normal_ext);
1824 i += normal_ext.length();
1826 return getTexture(fname_base, &id);