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);
899 // Drop scene manager
902 // Unset render target
903 driver->setRenderTarget(0, false, true, 0);
905 if(params.delete_texture_on_shutdown)
906 m_texture_trash.push_back(rtt);
911 video::IImage* TextureSource::generateImage(const std::string &name)
917 const char separator = '^';
918 const char paren_open = '(';
919 const char paren_close = ')';
921 // Find last separator in the name
922 s32 last_separator_pos = -1;
924 for(s32 i = name.size() - 1; i >= 0; i--) {
927 if (paren_bal == 0) {
928 last_separator_pos = i;
929 i = -1; // break out of loop
933 if (paren_bal == 0) {
934 errorstream << "generateImage(): unbalanced parentheses"
935 << "(extranous '(') while generating texture \""
936 << name << "\"" << std::endl;
949 errorstream << "generateImage(): unbalanced parentheses"
950 << "(missing matching '(') while generating texture \""
951 << name << "\"" << std::endl;
956 video::IImage *baseimg = NULL;
959 If separator was found, make the base image
960 using a recursive call.
962 if (last_separator_pos != -1) {
963 baseimg = generateImage(name.substr(0, last_separator_pos));
967 video::IVideoDriver* driver = m_device->getVideoDriver();
971 Parse out the last part of the name of the image and act
975 std::string last_part_of_name = name.substr(last_separator_pos + 1);
978 If this name is enclosed in parentheses, generate it
979 and blit it onto the base image
981 if (last_part_of_name[0] == paren_open
982 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
983 std::string name2 = last_part_of_name.substr(1,
984 last_part_of_name.size() - 2);
985 video::IImage *tmp = generateImage(name2);
987 errorstream << "generateImage(): "
988 "Failed to generate \"" << name2 << "\""
992 core::dimension2d<u32> dim = tmp->getDimension();
994 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
995 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
997 } else if (!generateImagePart(last_part_of_name, baseimg)) {
998 // Generate image according to part of name
999 errorstream << "generateImage(): "
1000 "Failed to generate \"" << last_part_of_name << "\""
1004 // If no resulting image, print a warning
1005 if (baseimg == NULL) {
1006 errorstream << "generateImage(): baseimg is NULL (attempted to"
1007 " create texture \"" << name << "\")" << std::endl;
1014 #include <GLES/gl.h>
1016 * Check and align image to npot2 if required by hardware
1017 * @param image image to check for npot2 alignment
1018 * @param driver driver to use for image operations
1019 * @return image or copy of image aligned to npot2
1021 video::IImage * Align2Npot2(video::IImage * image,
1022 video::IVideoDriver* driver)
1028 core::dimension2d<u32> dim = image->getDimension();
1030 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1031 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1035 unsigned int height = npot2(dim.Height);
1036 unsigned int width = npot2(dim.Width);
1038 if ((dim.Height == height) &&
1039 (dim.Width == width)) {
1043 if (dim.Height > height) {
1047 if (dim.Width > width) {
1051 video::IImage *targetimage =
1052 driver->createImage(video::ECF_A8R8G8B8,
1053 core::dimension2d<u32>(width, height));
1055 if (targetimage != NULL) {
1056 image->copyToScaling(targetimage);
1064 bool TextureSource::generateImagePart(std::string part_of_name,
1065 video::IImage *& baseimg)
1067 video::IVideoDriver* driver = m_device->getVideoDriver();
1070 // Stuff starting with [ are special commands
1071 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1073 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1075 image = Align2Npot2(image, driver);
1077 if (image == NULL) {
1078 if (part_of_name != "") {
1079 if (part_of_name.find("_normal.png") == std::string::npos){
1080 errorstream<<"generateImage(): Could not load image \""
1081 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1082 errorstream<<"generateImage(): Creating a dummy"
1083 <<" image for \""<<part_of_name<<"\""<<std::endl;
1085 infostream<<"generateImage(): Could not load normal map \""
1086 <<part_of_name<<"\""<<std::endl;
1087 infostream<<"generateImage(): Creating a dummy"
1088 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1092 // Just create a dummy image
1093 //core::dimension2d<u32> dim(2,2);
1094 core::dimension2d<u32> dim(1,1);
1095 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1097 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1098 image->setPixel(1,0, video::SColor(255,0,255,0));
1099 image->setPixel(0,1, video::SColor(255,0,0,255));
1100 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1101 image->setPixel(0,0, video::SColor(255,myrand()%256,
1102 myrand()%256,myrand()%256));
1103 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1104 myrand()%256,myrand()%256));
1105 image->setPixel(0,1, video::SColor(255,myrand()%256,
1106 myrand()%256,myrand()%256));
1107 image->setPixel(1,1, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));*/
1111 // If base image is NULL, load as base.
1114 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1116 Copy it this way to get an alpha channel.
1117 Otherwise images with alpha cannot be blitted on
1118 images that don't have alpha in the original file.
1120 core::dimension2d<u32> dim = image->getDimension();
1121 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1122 image->copyTo(baseimg);
1124 // Else blit on base.
1127 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1128 // Size of the copied area
1129 core::dimension2d<u32> dim = image->getDimension();
1130 //core::dimension2d<u32> dim(16,16);
1131 // Position to copy the blitted to in the base image
1132 core::position2d<s32> pos_to(0,0);
1133 // Position to copy the blitted from in the blitted image
1134 core::position2d<s32> pos_from(0,0);
1136 /*image->copyToWithAlpha(baseimg, pos_to,
1137 core::rect<s32>(pos_from, dim),
1138 video::SColor(255,255,255,255),
1140 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1147 // A special texture modification
1149 /*infostream<<"generateImage(): generating special "
1150 <<"modification \""<<part_of_name<<"\""
1156 Adds a cracking texture
1157 N = animation frame count, P = crack progression
1159 if(part_of_name.substr(0,6) == "[crack")
1161 if (baseimg == NULL) {
1162 errorstream<<"generateImagePart(): baseimg == NULL "
1163 <<"for part_of_name=\""<<part_of_name
1164 <<"\", cancelling."<<std::endl;
1168 // Crack image number and overlay option
1169 bool use_overlay = (part_of_name[6] == 'o');
1170 Strfnd sf(part_of_name);
1172 s32 frame_count = stoi(sf.next(":"));
1173 s32 progression = stoi(sf.next(":"));
1178 It is an image with a number of cracking stages
1181 video::IImage *img_crack = m_sourcecache.getOrLoad(
1182 "crack_anylength.png", m_device);
1184 if(img_crack && progression >= 0)
1186 draw_crack(img_crack, baseimg,
1187 use_overlay, frame_count,
1188 progression, driver);
1193 [combine:WxH:X,Y=filename:X,Y=filename2
1194 Creates a bigger texture from an amount of smaller ones
1196 else if(part_of_name.substr(0,8) == "[combine")
1198 Strfnd sf(part_of_name);
1200 u32 w0 = stoi(sf.next("x"));
1201 u32 h0 = stoi(sf.next(":"));
1202 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1203 core::dimension2d<u32> dim(w0,h0);
1204 if (baseimg == NULL) {
1205 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1206 baseimg->fill(video::SColor(0,0,0,0));
1208 while (sf.atend() == false) {
1209 u32 x = stoi(sf.next(","));
1210 u32 y = stoi(sf.next("="));
1211 std::string filename = sf.next(":");
1212 infostream<<"Adding \""<<filename
1213 <<"\" to combined ("<<x<<","<<y<<")"
1215 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1217 core::dimension2d<u32> dim = img->getDimension();
1218 infostream<<"Size "<<dim.Width
1219 <<"x"<<dim.Height<<std::endl;
1220 core::position2d<s32> pos_base(x, y);
1221 video::IImage *img2 =
1222 driver->createImage(video::ECF_A8R8G8B8, dim);
1225 /*img2->copyToWithAlpha(baseimg, pos_base,
1226 core::rect<s32>(v2s32(0,0), dim),
1227 video::SColor(255,255,255,255),
1229 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1232 errorstream << "generateImagePart(): Failed to load image \""
1233 << filename << "\" for [combine" << std::endl;
1240 else if(part_of_name.substr(0,9) == "[brighten")
1242 if (baseimg == NULL) {
1243 errorstream<<"generateImagePart(): baseimg==NULL "
1244 <<"for part_of_name=\""<<part_of_name
1245 <<"\", cancelling."<<std::endl;
1253 Make image completely opaque.
1254 Used for the leaves texture when in old leaves mode, so
1255 that the transparent parts don't look completely black
1256 when simple alpha channel is used for rendering.
1258 else if(part_of_name.substr(0,8) == "[noalpha")
1260 if (baseimg == NULL){
1261 errorstream<<"generateImagePart(): baseimg==NULL "
1262 <<"for part_of_name=\""<<part_of_name
1263 <<"\", cancelling."<<std::endl;
1267 core::dimension2d<u32> dim = baseimg->getDimension();
1269 // Set alpha to full
1270 for(u32 y=0; y<dim.Height; y++)
1271 for(u32 x=0; x<dim.Width; x++)
1273 video::SColor c = baseimg->getPixel(x,y);
1275 baseimg->setPixel(x,y,c);
1280 Convert one color to transparent.
1282 else if(part_of_name.substr(0,11) == "[makealpha:")
1284 if (baseimg == NULL) {
1285 errorstream<<"generateImagePart(): baseimg == NULL "
1286 <<"for part_of_name=\""<<part_of_name
1287 <<"\", cancelling."<<std::endl;
1291 Strfnd sf(part_of_name.substr(11));
1292 u32 r1 = stoi(sf.next(","));
1293 u32 g1 = stoi(sf.next(","));
1294 u32 b1 = stoi(sf.next(""));
1295 std::string filename = sf.next("");
1297 core::dimension2d<u32> dim = baseimg->getDimension();
1299 /*video::IImage *oldbaseimg = baseimg;
1300 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1301 oldbaseimg->copyTo(baseimg);
1302 oldbaseimg->drop();*/
1304 // Set alpha to full
1305 for(u32 y=0; y<dim.Height; y++)
1306 for(u32 x=0; x<dim.Width; x++)
1308 video::SColor c = baseimg->getPixel(x,y);
1310 u32 g = c.getGreen();
1311 u32 b = c.getBlue();
1312 if(!(r == r1 && g == g1 && b == b1))
1315 baseimg->setPixel(x,y,c);
1320 Rotates and/or flips the image.
1322 N can be a number (between 0 and 7) or a transform name.
1323 Rotations are counter-clockwise.
1325 1 R90 rotate by 90 degrees
1326 2 R180 rotate by 180 degrees
1327 3 R270 rotate by 270 degrees
1329 5 FXR90 flip X then rotate by 90 degrees
1331 7 FYR90 flip Y then rotate by 90 degrees
1333 Note: Transform names can be concatenated to produce
1334 their product (applies the first then the second).
1335 The resulting transform will be equivalent to one of the
1336 eight existing ones, though (see: dihedral group).
1338 else if(part_of_name.substr(0,10) == "[transform")
1340 if (baseimg == NULL) {
1341 errorstream<<"generateImagePart(): baseimg == NULL "
1342 <<"for part_of_name=\""<<part_of_name
1343 <<"\", cancelling."<<std::endl;
1347 u32 transform = parseImageTransform(part_of_name.substr(10));
1348 core::dimension2d<u32> dim = imageTransformDimension(
1349 transform, baseimg->getDimension());
1350 video::IImage *image = driver->createImage(
1351 baseimg->getColorFormat(), dim);
1353 imageTransform(transform, baseimg, image);
1358 [inventorycube{topimage{leftimage{rightimage
1359 In every subimage, replace ^ with &.
1360 Create an "inventory cube".
1361 NOTE: This should be used only on its own.
1362 Example (a grass block (not actually used in game):
1363 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1365 else if(part_of_name.substr(0,14) == "[inventorycube")
1367 if (baseimg != NULL){
1368 errorstream<<"generateImagePart(): baseimg != NULL "
1369 <<"for part_of_name=\""<<part_of_name
1370 <<"\", cancelling."<<std::endl;
1374 str_replace_char(part_of_name, '&', '^');
1375 Strfnd sf(part_of_name);
1377 std::string imagename_top = sf.next("{");
1378 std::string imagename_left = sf.next("{");
1379 std::string imagename_right = sf.next("{");
1381 // Generate images for the faces of the cube
1382 video::IImage *img_top = generateImage(imagename_top);
1383 video::IImage *img_left = generateImage(imagename_left);
1384 video::IImage *img_right = generateImage(imagename_right);
1386 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1387 errorstream << "generateImagePart(): Failed to create textures"
1388 << " for inventorycube \"" << part_of_name << "\""
1390 baseimg = generateImage(imagename_top);
1395 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1396 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1398 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1399 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1401 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1402 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1405 // Create textures from images
1406 video::ITexture *texture_top = driver->addTexture(
1407 (imagename_top + "__temp__").c_str(), img_top);
1408 video::ITexture *texture_left = driver->addTexture(
1409 (imagename_left + "__temp__").c_str(), img_left);
1410 video::ITexture *texture_right = driver->addTexture(
1411 (imagename_right + "__temp__").c_str(), img_right);
1412 assert(texture_top && texture_left && texture_right);
1420 Draw a cube mesh into a render target texture
1422 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1423 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1424 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1425 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1426 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1427 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1428 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1429 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1431 TextureFromMeshParams params;
1433 params.dim.set(64, 64);
1434 params.rtt_texture_name = part_of_name + "_RTT";
1435 // We will delete the rtt texture ourselves
1436 params.delete_texture_on_shutdown = false;
1437 params.camera_position.set(0, 1.0, -1.5);
1438 params.camera_position.rotateXZBy(45);
1439 params.camera_lookat.set(0, 0, 0);
1440 // Set orthogonal projection
1441 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1442 1.65, 1.65, 0, 100);
1444 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1445 params.light_position.set(10, 100, -50);
1446 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1447 params.light_radius = 1000;
1449 video::ITexture *rtt = generateTextureFromMesh(params);
1455 driver->removeTexture(texture_top);
1456 driver->removeTexture(texture_left);
1457 driver->removeTexture(texture_right);
1460 baseimg = generateImage(imagename_top);
1464 // Create image of render target
1465 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1469 driver->removeTexture(rtt);
1471 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1474 image->copyTo(baseimg);
1479 [lowpart:percent:filename
1480 Adds the lower part of a texture
1482 else if(part_of_name.substr(0,9) == "[lowpart:")
1484 Strfnd sf(part_of_name);
1486 u32 percent = stoi(sf.next(":"));
1487 std::string filename = sf.next(":");
1488 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1491 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1492 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1495 core::dimension2d<u32> dim = img->getDimension();
1496 core::position2d<s32> pos_base(0, 0);
1497 video::IImage *img2 =
1498 driver->createImage(video::ECF_A8R8G8B8, dim);
1501 core::position2d<s32> clippos(0, 0);
1502 clippos.Y = dim.Height * (100-percent) / 100;
1503 core::dimension2d<u32> clipdim = dim;
1504 clipdim.Height = clipdim.Height * percent / 100 + 1;
1505 core::rect<s32> cliprect(clippos, clipdim);
1506 img2->copyToWithAlpha(baseimg, pos_base,
1507 core::rect<s32>(v2s32(0,0), dim),
1508 video::SColor(255,255,255,255),
1515 Crops a frame of a vertical animation.
1516 N = frame count, I = frame index
1518 else if(part_of_name.substr(0,15) == "[verticalframe:")
1520 Strfnd sf(part_of_name);
1522 u32 frame_count = stoi(sf.next(":"));
1523 u32 frame_index = stoi(sf.next(":"));
1525 if(baseimg == NULL){
1526 errorstream<<"generateImagePart(): baseimg != NULL "
1527 <<"for part_of_name=\""<<part_of_name
1528 <<"\", cancelling."<<std::endl;
1532 v2u32 frame_size = baseimg->getDimension();
1533 frame_size.Y /= frame_count;
1535 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1538 errorstream<<"generateImagePart(): Could not create image "
1539 <<"for part_of_name=\""<<part_of_name
1540 <<"\", cancelling."<<std::endl;
1544 // Fill target image with transparency
1545 img->fill(video::SColor(0,0,0,0));
1547 core::dimension2d<u32> dim = frame_size;
1548 core::position2d<s32> pos_dst(0, 0);
1549 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1550 baseimg->copyToWithAlpha(img, pos_dst,
1551 core::rect<s32>(pos_src, dim),
1552 video::SColor(255,255,255,255),
1560 errorstream<<"generateImagePart(): Invalid "
1561 " modification: \""<<part_of_name<<"\""<<std::endl;
1569 Draw an image on top of an another one, using the alpha channel of the
1572 This exists because IImage::copyToWithAlpha() doesn't seem to always
1575 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1576 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1578 for(u32 y0=0; y0<size.Y; y0++)
1579 for(u32 x0=0; x0<size.X; x0++)
1581 s32 src_x = src_pos.X + x0;
1582 s32 src_y = src_pos.Y + y0;
1583 s32 dst_x = dst_pos.X + x0;
1584 s32 dst_y = dst_pos.Y + y0;
1585 video::SColor src_c = src->getPixel(src_x, src_y);
1586 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1587 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1588 dst->setPixel(dst_x, dst_y, dst_c);
1593 Draw an image on top of an another one, using the alpha channel of the
1594 source image; only modify fully opaque pixels in destinaion
1596 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1597 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1599 for(u32 y0=0; y0<size.Y; y0++)
1600 for(u32 x0=0; x0<size.X; x0++)
1602 s32 src_x = src_pos.X + x0;
1603 s32 src_y = src_pos.Y + y0;
1604 s32 dst_x = dst_pos.X + x0;
1605 s32 dst_y = dst_pos.Y + y0;
1606 video::SColor src_c = src->getPixel(src_x, src_y);
1607 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1608 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1610 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1611 dst->setPixel(dst_x, dst_y, dst_c);
1616 static void draw_crack(video::IImage *crack, video::IImage *dst,
1617 bool use_overlay, s32 frame_count, s32 progression,
1618 video::IVideoDriver *driver)
1620 // Dimension of destination image
1621 core::dimension2d<u32> dim_dst = dst->getDimension();
1622 // Dimension of original image
1623 core::dimension2d<u32> dim_crack = crack->getDimension();
1624 // Count of crack stages
1625 s32 crack_count = dim_crack.Height / dim_crack.Width;
1626 // Limit frame_count
1627 if(frame_count > (s32) dim_dst.Height)
1628 frame_count = dim_dst.Height;
1631 // Limit progression
1632 if(progression > crack_count-1)
1633 progression = crack_count-1;
1634 // Dimension of a single crack stage
1635 core::dimension2d<u32> dim_crack_cropped(
1639 // Dimension of the scaled crack stage,
1640 // which is the same as the dimension of a single destination frame
1641 core::dimension2d<u32> dim_crack_scaled(
1643 dim_dst.Height / frame_count
1645 // Create cropped and scaled crack images
1646 video::IImage *crack_cropped = driver->createImage(
1647 video::ECF_A8R8G8B8, dim_crack_cropped);
1648 video::IImage *crack_scaled = driver->createImage(
1649 video::ECF_A8R8G8B8, dim_crack_scaled);
1651 if(crack_cropped && crack_scaled)
1654 v2s32 pos_crack(0, progression*dim_crack.Width);
1655 crack->copyTo(crack_cropped,
1657 core::rect<s32>(pos_crack, dim_crack_cropped));
1658 // Scale crack image by copying
1659 crack_cropped->copyToScaling(crack_scaled);
1660 // Copy or overlay crack image onto each frame
1661 for(s32 i = 0; i < frame_count; ++i)
1663 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1666 blit_with_alpha_overlay(crack_scaled, dst,
1667 v2s32(0,0), dst_pos,
1672 blit_with_alpha(crack_scaled, dst,
1673 v2s32(0,0), dst_pos,
1680 crack_scaled->drop();
1683 crack_cropped->drop();
1686 void brighten(video::IImage *image)
1691 core::dimension2d<u32> dim = image->getDimension();
1693 for(u32 y=0; y<dim.Height; y++)
1694 for(u32 x=0; x<dim.Width; x++)
1696 video::SColor c = image->getPixel(x,y);
1697 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1698 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1699 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1700 image->setPixel(x,y,c);
1704 u32 parseImageTransform(const std::string& s)
1706 int total_transform = 0;
1708 std::string transform_names[8];
1709 transform_names[0] = "i";
1710 transform_names[1] = "r90";
1711 transform_names[2] = "r180";
1712 transform_names[3] = "r270";
1713 transform_names[4] = "fx";
1714 transform_names[6] = "fy";
1716 std::size_t pos = 0;
1717 while(pos < s.size())
1720 for(int i = 0; i <= 7; ++i)
1722 const std::string &name_i = transform_names[i];
1724 if(s[pos] == ('0' + i))
1730 else if(!(name_i.empty()) &&
1731 lowercase(s.substr(pos, name_i.size())) == name_i)
1734 pos += name_i.size();
1741 // Multiply total_transform and transform in the group D4
1744 new_total = (transform + total_transform) % 4;
1746 new_total = (transform - total_transform + 8) % 4;
1747 if((transform >= 4) ^ (total_transform >= 4))
1750 total_transform = new_total;
1752 return total_transform;
1755 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1757 if(transform % 2 == 0)
1760 return core::dimension2d<u32>(dim.Height, dim.Width);
1763 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1765 if(src == NULL || dst == NULL)
1768 core::dimension2d<u32> srcdim = src->getDimension();
1769 core::dimension2d<u32> dstdim = dst->getDimension();
1771 assert(dstdim == imageTransformDimension(transform, srcdim));
1772 assert(transform >= 0 && transform <= 7);
1775 Compute the transformation from source coordinates (sx,sy)
1776 to destination coordinates (dx,dy).
1780 if(transform == 0) // identity
1781 sxn = 0, syn = 2; // sx = dx, sy = dy
1782 else if(transform == 1) // rotate by 90 degrees ccw
1783 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1784 else if(transform == 2) // rotate by 180 degrees
1785 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1786 else if(transform == 3) // rotate by 270 degrees ccw
1787 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1788 else if(transform == 4) // flip x
1789 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1790 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1791 sxn = 2, syn = 0; // sx = dy, sy = dx
1792 else if(transform == 6) // flip y
1793 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1794 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1795 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1797 for(u32 dy=0; dy<dstdim.Height; dy++)
1798 for(u32 dx=0; dx<dstdim.Width; dx++)
1800 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1801 u32 sx = entries[sxn];
1802 u32 sy = entries[syn];
1803 video::SColor c = src->getPixel(sx,sy);
1804 dst->setPixel(dx,dy,c);
1808 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1811 if (isKnownSourceImage("override_normal.png"))
1812 return getTexture("override_normal.png", &id);
1813 std::string fname_base = name;
1814 std::string normal_ext = "_normal.png";
1815 size_t pos = fname_base.find(".");
1816 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1817 if (isKnownSourceImage(fname_normal)) {
1818 // look for image extension and replace it
1820 while ((i = fname_base.find(".", i)) != std::string::npos) {
1821 fname_base.replace(i, 4, normal_ext);
1822 i += normal_ext.length();
1824 return getTexture(fname_base, &id);