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.
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
29 #include "main.h" // for g_settings
42 A cache from texture name to texture path
44 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
47 Replaces the filename extension.
49 std::string image = "a/image.png"
50 replace_ext(image, "jpg")
51 -> image = "a/image.jpg"
52 Returns true on success.
54 static bool replace_ext(std::string &path, const char *ext)
58 // Find place of last dot, fail if \ or / found.
60 for(s32 i=path.size()-1; i>=0; i--)
68 if(path[i] == '\\' || path[i] == '/')
71 // If not found, return an empty string
74 // Else make the new path
75 path = path.substr(0, last_dot_i+1) + ext;
80 Find out the full path of an image by trying different filename
85 std::string getImagePath(std::string path)
87 // A NULL-ended list of possible image extensions
88 const char *extensions[] = {
89 "png", "jpg", "bmp", "tga",
90 "pcx", "ppm", "psd", "wal", "rgb",
93 // If there is no extension, add one
94 if(removeStringEnd(path, extensions) == "")
96 // Check paths until something is found to exist
97 const char **ext = extensions;
99 bool r = replace_ext(path, *ext);
102 if(fs::PathExists(path))
105 while((++ext) != NULL);
111 Gets the path to a texture by first checking if the texture exists
112 in texture_path and if not, using the data path.
114 Checks all supported extensions by replacing the original extension.
116 If not found, returns "".
118 Utilizes a thread-safe cache.
120 std::string getTexturePath(const std::string &filename)
122 std::string fullpath = "";
126 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
131 Check from texture_path
133 std::string texture_path = g_settings->get("texture_path");
134 if(texture_path != "")
136 std::string testpath = texture_path + DIR_DELIM + filename;
137 // Check all filename extensions. Returns "" if not found.
138 fullpath = getImagePath(testpath);
142 Check from default data directory
146 std::string base_path = porting::path_share + DIR_DELIM + "textures"
147 + DIR_DELIM + "base" + DIR_DELIM + "pack";
148 std::string testpath = base_path + DIR_DELIM + filename;
149 // Check all filename extensions. Returns "" if not found.
150 fullpath = getImagePath(testpath);
153 // Add to cache (also an empty result is cached)
154 g_texturename_to_path_cache.set(filename, fullpath);
160 void clearTextureNameCache()
162 g_texturename_to_path_cache.clear();
166 Stores internal information about a texture.
172 video::ITexture *texture;
175 const std::string &name_,
176 video::ITexture *texture_=NULL
185 SourceImageCache: A cache used for storing source images.
188 class SourceImageCache
191 ~SourceImageCache() {
192 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
193 iter != m_images.end(); iter++) {
194 iter->second->drop();
198 void insert(const std::string &name, video::IImage *img,
199 bool prefer_local, video::IVideoDriver *driver)
203 std::map<std::string, video::IImage*>::iterator n;
204 n = m_images.find(name);
205 if(n != m_images.end()){
210 video::IImage* toadd = img;
211 bool need_to_grab = true;
213 // Try to use local texture instead if asked to
215 std::string path = getTexturePath(name.c_str());
217 video::IImage *img2 = driver->createImageFromFile(path.c_str());
220 need_to_grab = false;
227 m_images[name] = toadd;
229 video::IImage* get(const std::string &name)
231 std::map<std::string, video::IImage*>::iterator n;
232 n = m_images.find(name);
233 if(n != m_images.end())
237 // Primarily fetches from cache, secondarily tries to read from filesystem
238 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
240 std::map<std::string, video::IImage*>::iterator n;
241 n = m_images.find(name);
242 if(n != m_images.end()){
243 n->second->grab(); // Grab for caller
246 video::IVideoDriver* driver = device->getVideoDriver();
247 std::string path = getTexturePath(name.c_str());
249 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
250 <<name<<"\""<<std::endl;
253 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
255 video::IImage *img = driver->createImageFromFile(path.c_str());
258 m_images[name] = img;
259 img->grab(); // Grab for caller
264 std::map<std::string, video::IImage*> m_images;
271 class TextureSource : public IWritableTextureSource
274 TextureSource(IrrlichtDevice *device);
275 virtual ~TextureSource();
279 Now, assume a texture with the id 1 exists, and has the name
280 "stone.png^mineral1".
281 Then a random thread calls getTextureId for a texture called
282 "stone.png^mineral1^crack0".
283 ...Now, WTF should happen? Well:
284 - getTextureId strips off stuff recursively from the end until
285 the remaining part is found, or nothing is left when
286 something is stripped out
288 But it is slow to search for textures by names and modify them
290 - ContentFeatures is made to contain ids for the basic plain
292 - Crack textures can be slow by themselves, but the framework
296 - Assume a texture with the id 1 exists, and has the name
297 "stone.png^mineral_coal.png".
298 - Now getNodeTile() stumbles upon a node which uses
299 texture id 1, and determines that MATERIAL_FLAG_CRACK
300 must be applied to the tile
301 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
302 has received the current crack level 0 from the client. It
303 finds out the name of the texture with getTextureName(1),
304 appends "^crack0" to it and gets a new texture id with
305 getTextureId("stone.png^mineral_coal.png^crack0").
310 Gets a texture id from cache or
311 - if main thread, generates the texture, adds to cache and returns id.
312 - if other thread, adds to request queue and waits for main thread.
314 The id 0 points to a NULL texture. It is returned in case of error.
316 u32 getTextureId(const std::string &name);
318 // Finds out the name of a cached texture.
319 std::string getTextureName(u32 id);
322 If texture specified by the name pointed by the id doesn't
323 exist, create it, then return the cached texture.
325 Can be called from any thread. If called from some other thread
326 and not found in cache, the call is queued to the main thread
329 video::ITexture* getTexture(u32 id);
331 video::ITexture* getTexture(const std::string &name, u32 *id);
333 // Returns a pointer to the irrlicht device
334 virtual IrrlichtDevice* getDevice()
339 bool isKnownSourceImage(const std::string &name)
341 bool is_known = false;
342 bool cache_found = m_source_image_existence.get(name, &is_known);
345 // Not found in cache; find out if a local file exists
346 is_known = (getTexturePath(name) != "");
347 m_source_image_existence.set(name, is_known);
351 // Processes queued texture requests from other threads.
352 // Shall be called from the main thread.
355 // Insert an image into the cache without touching the filesystem.
356 // Shall be called from the main thread.
357 void insertSourceImage(const std::string &name, video::IImage *img);
359 // Rebuild images and textures from the current set of source images
360 // Shall be called from the main thread.
361 void rebuildImagesAndTextures();
363 // Render a mesh to a texture.
364 // Returns NULL if render-to-texture failed.
365 // Shall be called from the main thread.
366 video::ITexture* generateTextureFromMesh(
367 const TextureFromMeshParams ¶ms);
369 // Generates an image from a full string like
370 // "stone.png^mineral_coal.png^[crack:1:0".
371 // Shall be called from the main thread.
372 video::IImage* generateImage(const std::string &name);
374 video::ITexture* getNormalTexture(const std::string &name);
377 // The id of the thread that is allowed to use irrlicht directly
378 threadid_t m_main_thread;
379 // The irrlicht device
380 IrrlichtDevice *m_device;
382 // Cache of source images
383 // This should be only accessed from the main thread
384 SourceImageCache m_sourcecache;
386 // Generate a texture
387 u32 generateTexture(const std::string &name);
389 // Generate image based on a string like "stone.png" or "[crack:1:0".
390 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
391 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
393 // Thread-safe cache of what source images are known (true = known)
394 MutexedMap<std::string, bool> m_source_image_existence;
396 // A texture id is index in this array.
397 // The first position contains a NULL texture.
398 std::vector<TextureInfo> m_textureinfo_cache;
399 // Maps a texture name to an index in the former.
400 std::map<std::string, u32> m_name_to_id;
401 // The two former containers are behind this mutex
402 JMutex m_textureinfo_cache_mutex;
404 // Queued texture fetches (to be processed by the main thread)
405 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
407 // Textures that have been overwritten with other ones
408 // but can't be deleted because the ITexture* might still be used
409 std::list<video::ITexture*> m_texture_trash;
411 // Cached settings needed for making textures from meshes
412 bool m_setting_trilinear_filter;
413 bool m_setting_bilinear_filter;
414 bool m_setting_anisotropic_filter;
417 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
419 return new TextureSource(device);
422 TextureSource::TextureSource(IrrlichtDevice *device):
427 m_main_thread = get_current_thread_id();
429 // Add a NULL TextureInfo as the first index, named ""
430 m_textureinfo_cache.push_back(TextureInfo(""));
431 m_name_to_id[""] = 0;
433 // Cache some settings
434 // Note: Since this is only done once, the game must be restarted
435 // for these settings to take effect
436 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
437 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
438 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
441 TextureSource::~TextureSource()
443 video::IVideoDriver* driver = m_device->getVideoDriver();
445 unsigned int textures_before = driver->getTextureCount();
447 for (std::vector<TextureInfo>::iterator iter =
448 m_textureinfo_cache.begin();
449 iter != m_textureinfo_cache.end(); iter++)
453 driver->removeTexture(iter->texture);
455 m_textureinfo_cache.clear();
457 for (std::list<video::ITexture*>::iterator iter =
458 m_texture_trash.begin(); iter != m_texture_trash.end();
461 video::ITexture *t = *iter;
463 //cleanup trashed texture
464 driver->removeTexture(t);
467 infostream << "~TextureSource() "<< textures_before << "/"
468 << driver->getTextureCount() << std::endl;
471 u32 TextureSource::getTextureId(const std::string &name)
473 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
477 See if texture already exists
479 JMutexAutoLock lock(m_textureinfo_cache_mutex);
480 std::map<std::string, u32>::iterator n;
481 n = m_name_to_id.find(name);
482 if(n != m_name_to_id.end())
491 if(get_current_thread_id() == m_main_thread)
493 return generateTexture(name);
497 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
499 // We're gonna ask the result to be put into here
500 static ResultQueue<std::string, u32, u8, u8> result_queue;
502 // Throw a request in
503 m_get_texture_queue.add(name, 0, 0, &result_queue);
505 /*infostream<<"Waiting for texture from main thread, name=\""
506 <<name<<"\""<<std::endl;*/
511 // Wait result for a second
512 GetResult<std::string, u32, u8, u8>
513 result = result_queue.pop_front(1000);
515 if (result.key == name) {
520 catch(ItemNotFoundException &e)
522 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
527 infostream<<"getTextureId(): Failed"<<std::endl;
532 // Draw an image on top of an another one, using the alpha channel of the
534 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
535 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
537 // Like blit_with_alpha, but only modifies destination pixels that
539 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
540 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
542 // Apply a mask to an image
543 static void apply_mask(video::IImage *mask, video::IImage *dst,
544 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
546 // Draw or overlay a crack
547 static void draw_crack(video::IImage *crack, video::IImage *dst,
548 bool use_overlay, s32 frame_count, s32 progression,
549 video::IVideoDriver *driver);
552 void brighten(video::IImage *image);
553 // Parse a transform name
554 u32 parseImageTransform(const std::string& s);
555 // Apply transform to image dimension
556 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
557 // Apply transform to image data
558 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
561 This method generates all the textures
563 u32 TextureSource::generateTexture(const std::string &name)
565 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
567 // Empty name means texture 0
569 infostream<<"generateTexture(): name is empty"<<std::endl;
575 See if texture already exists
577 JMutexAutoLock lock(m_textureinfo_cache_mutex);
578 std::map<std::string, u32>::iterator n;
579 n = m_name_to_id.find(name);
580 if (n != m_name_to_id.end()) {
586 Calling only allowed from main thread
588 if (get_current_thread_id() != m_main_thread) {
589 errorstream<<"TextureSource::generateTexture() "
590 "called not from main thread"<<std::endl;
594 video::IVideoDriver *driver = m_device->getVideoDriver();
597 video::IImage *img = generateImage(name);
599 video::ITexture *tex = NULL;
603 img = Align2Npot2(img, driver);
605 // Create texture from resulting image
606 tex = driver->addTexture(name.c_str(), img);
611 Add texture to caches (add NULL textures too)
614 JMutexAutoLock lock(m_textureinfo_cache_mutex);
616 u32 id = m_textureinfo_cache.size();
617 TextureInfo ti(name, tex);
618 m_textureinfo_cache.push_back(ti);
619 m_name_to_id[name] = id;
624 std::string TextureSource::getTextureName(u32 id)
626 JMutexAutoLock lock(m_textureinfo_cache_mutex);
628 if(id >= m_textureinfo_cache.size())
630 errorstream<<"TextureSource::getTextureName(): id="<<id
631 <<" >= m_textureinfo_cache.size()="
632 <<m_textureinfo_cache.size()<<std::endl;
636 return m_textureinfo_cache[id].name;
639 video::ITexture* TextureSource::getTexture(u32 id)
641 JMutexAutoLock lock(m_textureinfo_cache_mutex);
643 if(id >= m_textureinfo_cache.size())
646 return m_textureinfo_cache[id].texture;
649 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
651 u32 actual_id = getTextureId(name);
655 return getTexture(actual_id);
658 void TextureSource::processQueue()
663 //NOTE this is only thread safe for ONE consumer thread!
664 if(!m_get_texture_queue.empty())
666 GetRequest<std::string, u32, u8, u8>
667 request = m_get_texture_queue.pop();
669 /*infostream<<"TextureSource::processQueue(): "
670 <<"got texture request with "
671 <<"name=\""<<request.key<<"\""
674 m_get_texture_queue.pushResult(request, generateTexture(request.key));
678 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
680 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
682 assert(get_current_thread_id() == m_main_thread);
684 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
685 m_source_image_existence.set(name, true);
688 void TextureSource::rebuildImagesAndTextures()
690 JMutexAutoLock lock(m_textureinfo_cache_mutex);
692 video::IVideoDriver* driver = m_device->getVideoDriver();
696 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
697 TextureInfo *ti = &m_textureinfo_cache[i];
698 video::IImage *img = generateImage(ti->name);
700 img = Align2Npot2(img, driver);
701 assert(img->getDimension().Height == npot2(img->getDimension().Height));
702 assert(img->getDimension().Width == npot2(img->getDimension().Width));
704 // Create texture from resulting image
705 video::ITexture *t = NULL;
707 t = driver->addTexture(ti->name.c_str(), img);
710 video::ITexture *t_old = ti->texture;
715 m_texture_trash.push_back(t_old);
719 video::ITexture* TextureSource::generateTextureFromMesh(
720 const TextureFromMeshParams ¶ms)
722 video::IVideoDriver *driver = m_device->getVideoDriver();
726 const GLubyte* renderstr = glGetString(GL_RENDERER);
727 std::string renderer((char*) renderstr);
729 // use no render to texture hack
731 (renderer.find("Adreno") != std::string::npos) ||
732 (renderer.find("Mali") != std::string::npos) ||
733 (renderer.find("Immersion") != std::string::npos) ||
734 (renderer.find("Tegra") != std::string::npos) ||
735 g_settings->getBool("inventory_image_hack")
737 // Get a scene manager
738 scene::ISceneManager *smgr_main = m_device->getSceneManager();
740 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
743 const float scaling = 0.2;
745 scene::IMeshSceneNode* meshnode =
746 smgr->addMeshSceneNode(params.mesh, NULL,
747 -1, v3f(0,0,0), v3f(0,0,0),
748 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
749 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
750 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
751 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
752 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
753 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
755 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
756 params.camera_position, params.camera_lookat);
757 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
758 camera->setProjectionMatrix(params.camera_projection_matrix, false);
760 smgr->setAmbientLight(params.ambient_light);
761 smgr->addLightSceneNode(0,
762 params.light_position,
764 params.light_radius*scaling);
766 core::dimension2d<u32> screen = driver->getScreenSize();
769 driver->beginScene(true, true, video::SColor(0,0,0,0));
770 driver->clearZBuffer();
773 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
775 irr::video::IImage* rawImage =
776 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
778 u8* pixels = static_cast<u8*>(rawImage->lock());
785 core::rect<s32> source(
786 screen.Width /2 - (screen.Width * (scaling / 2)),
787 screen.Height/2 - (screen.Height * (scaling / 2)),
788 screen.Width /2 + (screen.Width * (scaling / 2)),
789 screen.Height/2 + (screen.Height * (scaling / 2))
792 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
793 partsize.Width, partsize.Height, GL_RGBA,
794 GL_UNSIGNED_BYTE, pixels);
798 // Drop scene manager
801 unsigned int pixelcount = partsize.Width*partsize.Height;
804 for (unsigned int i=0; i < pixelcount; i++) {
822 video::IImage* inventory_image =
823 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
825 rawImage->copyToScaling(inventory_image);
828 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
829 inventory_image->drop();
832 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
836 driver->makeColorKeyTexture(rtt, v2s32(0,0));
838 if(params.delete_texture_on_shutdown)
839 m_texture_trash.push_back(rtt);
845 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
847 static bool warned = false;
850 errorstream<<"TextureSource::generateTextureFromMesh(): "
851 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
857 // Create render target texture
858 video::ITexture *rtt = driver->addRenderTargetTexture(
859 params.dim, params.rtt_texture_name.c_str(),
860 video::ECF_A8R8G8B8);
863 errorstream<<"TextureSource::generateTextureFromMesh(): "
864 <<"addRenderTargetTexture returned NULL."<<std::endl;
869 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
870 driver->removeTexture(rtt);
871 errorstream<<"TextureSource::generateTextureFromMesh(): "
872 <<"failed to set render target"<<std::endl;
876 // Get a scene manager
877 scene::ISceneManager *smgr_main = m_device->getSceneManager();
879 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
882 scene::IMeshSceneNode* meshnode =
883 smgr->addMeshSceneNode(params.mesh, NULL,
884 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
885 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
886 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
887 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
888 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
889 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
891 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
892 params.camera_position, params.camera_lookat);
893 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
894 camera->setProjectionMatrix(params.camera_projection_matrix, false);
896 smgr->setAmbientLight(params.ambient_light);
897 smgr->addLightSceneNode(0,
898 params.light_position,
900 params.light_radius);
903 driver->beginScene(true, true, video::SColor(0,0,0,0));
907 // Drop scene manager
910 // Unset render target
911 driver->setRenderTarget(0, false, true, 0);
913 if(params.delete_texture_on_shutdown)
914 m_texture_trash.push_back(rtt);
919 video::IImage* TextureSource::generateImage(const std::string &name)
925 const char separator = '^';
926 const char paren_open = '(';
927 const char paren_close = ')';
929 // Find last separator in the name
930 s32 last_separator_pos = -1;
932 for(s32 i = name.size() - 1; i >= 0; i--) {
935 if (paren_bal == 0) {
936 last_separator_pos = i;
937 i = -1; // break out of loop
941 if (paren_bal == 0) {
942 errorstream << "generateImage(): unbalanced parentheses"
943 << "(extranous '(') while generating texture \""
944 << name << "\"" << std::endl;
957 errorstream << "generateImage(): unbalanced parentheses"
958 << "(missing matching '(') while generating texture \""
959 << name << "\"" << std::endl;
964 video::IImage *baseimg = NULL;
967 If separator was found, make the base image
968 using a recursive call.
970 if (last_separator_pos != -1) {
971 baseimg = generateImage(name.substr(0, last_separator_pos));
975 video::IVideoDriver* driver = m_device->getVideoDriver();
979 Parse out the last part of the name of the image and act
983 std::string last_part_of_name = name.substr(last_separator_pos + 1);
986 If this name is enclosed in parentheses, generate it
987 and blit it onto the base image
989 if (last_part_of_name[0] == paren_open
990 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
991 std::string name2 = last_part_of_name.substr(1,
992 last_part_of_name.size() - 2);
993 video::IImage *tmp = generateImage(name2);
995 errorstream << "generateImage(): "
996 "Failed to generate \"" << name2 << "\""
1000 core::dimension2d<u32> dim = tmp->getDimension();
1002 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1003 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1005 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1006 // Generate image according to part of name
1007 errorstream << "generateImage(): "
1008 "Failed to generate \"" << last_part_of_name << "\""
1012 // If no resulting image, print a warning
1013 if (baseimg == NULL) {
1014 errorstream << "generateImage(): baseimg is NULL (attempted to"
1015 " create texture \"" << name << "\")" << std::endl;
1022 #include <GLES/gl.h>
1024 * Check and align image to npot2 if required by hardware
1025 * @param image image to check for npot2 alignment
1026 * @param driver driver to use for image operations
1027 * @return image or copy of image aligned to npot2
1029 video::IImage * Align2Npot2(video::IImage * image,
1030 video::IVideoDriver* driver)
1036 core::dimension2d<u32> dim = image->getDimension();
1038 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1039 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1043 unsigned int height = npot2(dim.Height);
1044 unsigned int width = npot2(dim.Width);
1046 if ((dim.Height == height) &&
1047 (dim.Width == width)) {
1051 if (dim.Height > height) {
1055 if (dim.Width > width) {
1059 video::IImage *targetimage =
1060 driver->createImage(video::ECF_A8R8G8B8,
1061 core::dimension2d<u32>(width, height));
1063 if (targetimage != NULL) {
1064 image->copyToScaling(targetimage);
1072 bool TextureSource::generateImagePart(std::string part_of_name,
1073 video::IImage *& baseimg)
1075 video::IVideoDriver* driver = m_device->getVideoDriver();
1078 // Stuff starting with [ are special commands
1079 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1081 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1083 image = Align2Npot2(image, driver);
1085 if (image == NULL) {
1086 if (part_of_name != "") {
1087 if (part_of_name.find("_normal.png") == std::string::npos){
1088 errorstream<<"generateImage(): Could not load image \""
1089 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1090 errorstream<<"generateImage(): Creating a dummy"
1091 <<" image for \""<<part_of_name<<"\""<<std::endl;
1093 infostream<<"generateImage(): Could not load normal map \""
1094 <<part_of_name<<"\""<<std::endl;
1095 infostream<<"generateImage(): Creating a dummy"
1096 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1100 // Just create a dummy image
1101 //core::dimension2d<u32> dim(2,2);
1102 core::dimension2d<u32> dim(1,1);
1103 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1105 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1106 image->setPixel(1,0, video::SColor(255,0,255,0));
1107 image->setPixel(0,1, video::SColor(255,0,0,255));
1108 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1109 image->setPixel(0,0, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));
1111 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));
1113 image->setPixel(0,1, video::SColor(255,myrand()%256,
1114 myrand()%256,myrand()%256));
1115 image->setPixel(1,1, video::SColor(255,myrand()%256,
1116 myrand()%256,myrand()%256));*/
1119 // If base image is NULL, load as base.
1122 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1124 Copy it this way to get an alpha channel.
1125 Otherwise images with alpha cannot be blitted on
1126 images that don't have alpha in the original file.
1128 core::dimension2d<u32> dim = image->getDimension();
1129 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1130 image->copyTo(baseimg);
1132 // Else blit on base.
1135 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1136 // Size of the copied area
1137 core::dimension2d<u32> dim = image->getDimension();
1138 //core::dimension2d<u32> dim(16,16);
1139 // Position to copy the blitted to in the base image
1140 core::position2d<s32> pos_to(0,0);
1141 // Position to copy the blitted from in the blitted image
1142 core::position2d<s32> pos_from(0,0);
1144 /*image->copyToWithAlpha(baseimg, pos_to,
1145 core::rect<s32>(pos_from, dim),
1146 video::SColor(255,255,255,255),
1148 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1155 // A special texture modification
1157 /*infostream<<"generateImage(): generating special "
1158 <<"modification \""<<part_of_name<<"\""
1164 Adds a cracking texture
1165 N = animation frame count, P = crack progression
1167 if(part_of_name.substr(0,6) == "[crack")
1169 if (baseimg == NULL) {
1170 errorstream<<"generateImagePart(): baseimg == NULL "
1171 <<"for part_of_name=\""<<part_of_name
1172 <<"\", cancelling."<<std::endl;
1176 // Crack image number and overlay option
1177 bool use_overlay = (part_of_name[6] == 'o');
1178 Strfnd sf(part_of_name);
1180 s32 frame_count = stoi(sf.next(":"));
1181 s32 progression = stoi(sf.next(":"));
1186 It is an image with a number of cracking stages
1189 video::IImage *img_crack = m_sourcecache.getOrLoad(
1190 "crack_anylength.png", m_device);
1192 if(img_crack && progression >= 0)
1194 draw_crack(img_crack, baseimg,
1195 use_overlay, frame_count,
1196 progression, driver);
1201 [combine:WxH:X,Y=filename:X,Y=filename2
1202 Creates a bigger texture from an amount of smaller ones
1204 else if(part_of_name.substr(0,8) == "[combine")
1206 Strfnd sf(part_of_name);
1208 u32 w0 = stoi(sf.next("x"));
1209 u32 h0 = stoi(sf.next(":"));
1210 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1211 core::dimension2d<u32> dim(w0,h0);
1212 if (baseimg == NULL) {
1213 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1214 baseimg->fill(video::SColor(0,0,0,0));
1216 while (sf.atend() == false) {
1217 u32 x = stoi(sf.next(","));
1218 u32 y = stoi(sf.next("="));
1219 std::string filename = sf.next(":");
1220 infostream<<"Adding \""<<filename
1221 <<"\" to combined ("<<x<<","<<y<<")"
1223 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1225 core::dimension2d<u32> dim = img->getDimension();
1226 infostream<<"Size "<<dim.Width
1227 <<"x"<<dim.Height<<std::endl;
1228 core::position2d<s32> pos_base(x, y);
1229 video::IImage *img2 =
1230 driver->createImage(video::ECF_A8R8G8B8, dim);
1233 /*img2->copyToWithAlpha(baseimg, pos_base,
1234 core::rect<s32>(v2s32(0,0), dim),
1235 video::SColor(255,255,255,255),
1237 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1240 errorstream << "generateImagePart(): Failed to load image \""
1241 << filename << "\" for [combine" << std::endl;
1248 else if(part_of_name.substr(0,9) == "[brighten")
1250 if (baseimg == NULL) {
1251 errorstream<<"generateImagePart(): baseimg==NULL "
1252 <<"for part_of_name=\""<<part_of_name
1253 <<"\", cancelling."<<std::endl;
1261 Make image completely opaque.
1262 Used for the leaves texture when in old leaves mode, so
1263 that the transparent parts don't look completely black
1264 when simple alpha channel is used for rendering.
1266 else if(part_of_name.substr(0,8) == "[noalpha")
1268 if (baseimg == NULL){
1269 errorstream<<"generateImagePart(): baseimg==NULL "
1270 <<"for part_of_name=\""<<part_of_name
1271 <<"\", cancelling."<<std::endl;
1275 core::dimension2d<u32> dim = baseimg->getDimension();
1277 // Set alpha to full
1278 for(u32 y=0; y<dim.Height; y++)
1279 for(u32 x=0; x<dim.Width; x++)
1281 video::SColor c = baseimg->getPixel(x,y);
1283 baseimg->setPixel(x,y,c);
1288 Convert one color to transparent.
1290 else if(part_of_name.substr(0,11) == "[makealpha:")
1292 if (baseimg == NULL) {
1293 errorstream<<"generateImagePart(): baseimg == NULL "
1294 <<"for part_of_name=\""<<part_of_name
1295 <<"\", cancelling."<<std::endl;
1299 Strfnd sf(part_of_name.substr(11));
1300 u32 r1 = stoi(sf.next(","));
1301 u32 g1 = stoi(sf.next(","));
1302 u32 b1 = stoi(sf.next(""));
1303 std::string filename = sf.next("");
1305 core::dimension2d<u32> dim = baseimg->getDimension();
1307 /*video::IImage *oldbaseimg = baseimg;
1308 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1309 oldbaseimg->copyTo(baseimg);
1310 oldbaseimg->drop();*/
1312 // Set alpha to full
1313 for(u32 y=0; y<dim.Height; y++)
1314 for(u32 x=0; x<dim.Width; x++)
1316 video::SColor c = baseimg->getPixel(x,y);
1318 u32 g = c.getGreen();
1319 u32 b = c.getBlue();
1320 if(!(r == r1 && g == g1 && b == b1))
1323 baseimg->setPixel(x,y,c);
1328 Rotates and/or flips the image.
1330 N can be a number (between 0 and 7) or a transform name.
1331 Rotations are counter-clockwise.
1333 1 R90 rotate by 90 degrees
1334 2 R180 rotate by 180 degrees
1335 3 R270 rotate by 270 degrees
1337 5 FXR90 flip X then rotate by 90 degrees
1339 7 FYR90 flip Y then rotate by 90 degrees
1341 Note: Transform names can be concatenated to produce
1342 their product (applies the first then the second).
1343 The resulting transform will be equivalent to one of the
1344 eight existing ones, though (see: dihedral group).
1346 else if(part_of_name.substr(0,10) == "[transform")
1348 if (baseimg == NULL) {
1349 errorstream<<"generateImagePart(): baseimg == NULL "
1350 <<"for part_of_name=\""<<part_of_name
1351 <<"\", cancelling."<<std::endl;
1355 u32 transform = parseImageTransform(part_of_name.substr(10));
1356 core::dimension2d<u32> dim = imageTransformDimension(
1357 transform, baseimg->getDimension());
1358 video::IImage *image = driver->createImage(
1359 baseimg->getColorFormat(), dim);
1361 imageTransform(transform, baseimg, image);
1366 [inventorycube{topimage{leftimage{rightimage
1367 In every subimage, replace ^ with &.
1368 Create an "inventory cube".
1369 NOTE: This should be used only on its own.
1370 Example (a grass block (not actually used in game):
1371 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1373 else if(part_of_name.substr(0,14) == "[inventorycube")
1375 if (baseimg != NULL){
1376 errorstream<<"generateImagePart(): baseimg != NULL "
1377 <<"for part_of_name=\""<<part_of_name
1378 <<"\", cancelling."<<std::endl;
1382 str_replace_char(part_of_name, '&', '^');
1383 Strfnd sf(part_of_name);
1385 std::string imagename_top = sf.next("{");
1386 std::string imagename_left = sf.next("{");
1387 std::string imagename_right = sf.next("{");
1389 // Generate images for the faces of the cube
1390 video::IImage *img_top = generateImage(imagename_top);
1391 video::IImage *img_left = generateImage(imagename_left);
1392 video::IImage *img_right = generateImage(imagename_right);
1394 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1395 errorstream << "generateImagePart(): Failed to create textures"
1396 << " for inventorycube \"" << part_of_name << "\""
1398 baseimg = generateImage(imagename_top);
1403 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1404 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1406 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1407 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1409 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1410 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1413 // Create textures from images
1414 video::ITexture *texture_top = driver->addTexture(
1415 (imagename_top + "__temp__").c_str(), img_top);
1416 video::ITexture *texture_left = driver->addTexture(
1417 (imagename_left + "__temp__").c_str(), img_left);
1418 video::ITexture *texture_right = driver->addTexture(
1419 (imagename_right + "__temp__").c_str(), img_right);
1420 assert(texture_top && texture_left && texture_right);
1428 Draw a cube mesh into a render target texture
1430 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1431 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1432 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1433 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1434 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1435 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1436 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1437 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1439 TextureFromMeshParams params;
1441 params.dim.set(64, 64);
1442 params.rtt_texture_name = part_of_name + "_RTT";
1443 // We will delete the rtt texture ourselves
1444 params.delete_texture_on_shutdown = false;
1445 params.camera_position.set(0, 1.0, -1.5);
1446 params.camera_position.rotateXZBy(45);
1447 params.camera_lookat.set(0, 0, 0);
1448 // Set orthogonal projection
1449 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1450 1.65, 1.65, 0, 100);
1452 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1453 params.light_position.set(10, 100, -50);
1454 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1455 params.light_radius = 1000;
1457 video::ITexture *rtt = generateTextureFromMesh(params);
1463 driver->removeTexture(texture_top);
1464 driver->removeTexture(texture_left);
1465 driver->removeTexture(texture_right);
1468 baseimg = generateImage(imagename_top);
1472 // Create image of render target
1473 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1477 driver->removeTexture(rtt);
1479 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1482 image->copyTo(baseimg);
1487 [lowpart:percent:filename
1488 Adds the lower part of a texture
1490 else if(part_of_name.substr(0,9) == "[lowpart:")
1492 Strfnd sf(part_of_name);
1494 u32 percent = stoi(sf.next(":"));
1495 std::string filename = sf.next(":");
1496 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1499 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1500 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1503 core::dimension2d<u32> dim = img->getDimension();
1504 core::position2d<s32> pos_base(0, 0);
1505 video::IImage *img2 =
1506 driver->createImage(video::ECF_A8R8G8B8, dim);
1509 core::position2d<s32> clippos(0, 0);
1510 clippos.Y = dim.Height * (100-percent) / 100;
1511 core::dimension2d<u32> clipdim = dim;
1512 clipdim.Height = clipdim.Height * percent / 100 + 1;
1513 core::rect<s32> cliprect(clippos, clipdim);
1514 img2->copyToWithAlpha(baseimg, pos_base,
1515 core::rect<s32>(v2s32(0,0), dim),
1516 video::SColor(255,255,255,255),
1523 Crops a frame of a vertical animation.
1524 N = frame count, I = frame index
1526 else if(part_of_name.substr(0,15) == "[verticalframe:")
1528 Strfnd sf(part_of_name);
1530 u32 frame_count = stoi(sf.next(":"));
1531 u32 frame_index = stoi(sf.next(":"));
1533 if(baseimg == NULL){
1534 errorstream<<"generateImagePart(): baseimg != NULL "
1535 <<"for part_of_name=\""<<part_of_name
1536 <<"\", cancelling."<<std::endl;
1540 v2u32 frame_size = baseimg->getDimension();
1541 frame_size.Y /= frame_count;
1543 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1546 errorstream<<"generateImagePart(): Could not create image "
1547 <<"for part_of_name=\""<<part_of_name
1548 <<"\", cancelling."<<std::endl;
1552 // Fill target image with transparency
1553 img->fill(video::SColor(0,0,0,0));
1555 core::dimension2d<u32> dim = frame_size;
1556 core::position2d<s32> pos_dst(0, 0);
1557 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1558 baseimg->copyToWithAlpha(img, pos_dst,
1559 core::rect<s32>(pos_src, dim),
1560 video::SColor(255,255,255,255),
1568 Applies a mask to an image
1570 else if(part_of_name.substr(0,6) == "[mask:")
1572 if (baseimg == NULL) {
1573 errorstream << "generateImage(): baseimg == NULL "
1574 << "for part_of_name=\"" << part_of_name
1575 << "\", cancelling." << std::endl;
1578 Strfnd sf(part_of_name);
1580 std::string filename = sf.next(":");
1582 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1584 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1585 img->getDimension());
1587 errorstream << "generateImage(): Failed to load \""
1588 << filename << "\".";
1593 errorstream<<"generateImagePart(): Invalid "
1594 " modification: \""<<part_of_name<<"\""<<std::endl;
1602 Draw an image on top of an another one, using the alpha channel of the
1605 This exists because IImage::copyToWithAlpha() doesn't seem to always
1608 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1609 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1611 for(u32 y0=0; y0<size.Y; y0++)
1612 for(u32 x0=0; x0<size.X; x0++)
1614 s32 src_x = src_pos.X + x0;
1615 s32 src_y = src_pos.Y + y0;
1616 s32 dst_x = dst_pos.X + x0;
1617 s32 dst_y = dst_pos.Y + y0;
1618 video::SColor src_c = src->getPixel(src_x, src_y);
1619 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1620 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1621 dst->setPixel(dst_x, dst_y, dst_c);
1626 Draw an image on top of an another one, using the alpha channel of the
1627 source image; only modify fully opaque pixels in destinaion
1629 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1630 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1632 for(u32 y0=0; y0<size.Y; y0++)
1633 for(u32 x0=0; x0<size.X; x0++)
1635 s32 src_x = src_pos.X + x0;
1636 s32 src_y = src_pos.Y + y0;
1637 s32 dst_x = dst_pos.X + x0;
1638 s32 dst_y = dst_pos.Y + y0;
1639 video::SColor src_c = src->getPixel(src_x, src_y);
1640 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1641 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1643 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1644 dst->setPixel(dst_x, dst_y, dst_c);
1650 Apply mask to destination
1652 static void apply_mask(video::IImage *mask, video::IImage *dst,
1653 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1655 for(u32 y0 = 0; y0 < size.Y; y0++) {
1656 for(u32 x0 = 0; x0 < size.X; x0++) {
1657 s32 mask_x = x0 + mask_pos.X;
1658 s32 mask_y = y0 + mask_pos.Y;
1659 s32 dst_x = x0 + dst_pos.X;
1660 s32 dst_y = y0 + dst_pos.Y;
1661 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1662 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1663 dst_c.color &= mask_c.color;
1664 dst->setPixel(dst_x, dst_y, dst_c);
1669 static void draw_crack(video::IImage *crack, video::IImage *dst,
1670 bool use_overlay, s32 frame_count, s32 progression,
1671 video::IVideoDriver *driver)
1673 // Dimension of destination image
1674 core::dimension2d<u32> dim_dst = dst->getDimension();
1675 // Dimension of original image
1676 core::dimension2d<u32> dim_crack = crack->getDimension();
1677 // Count of crack stages
1678 s32 crack_count = dim_crack.Height / dim_crack.Width;
1679 // Limit frame_count
1680 if(frame_count > (s32) dim_dst.Height)
1681 frame_count = dim_dst.Height;
1684 // Limit progression
1685 if(progression > crack_count-1)
1686 progression = crack_count-1;
1687 // Dimension of a single crack stage
1688 core::dimension2d<u32> dim_crack_cropped(
1692 // Dimension of the scaled crack stage,
1693 // which is the same as the dimension of a single destination frame
1694 core::dimension2d<u32> dim_crack_scaled(
1696 dim_dst.Height / frame_count
1698 // Create cropped and scaled crack images
1699 video::IImage *crack_cropped = driver->createImage(
1700 video::ECF_A8R8G8B8, dim_crack_cropped);
1701 video::IImage *crack_scaled = driver->createImage(
1702 video::ECF_A8R8G8B8, dim_crack_scaled);
1704 if(crack_cropped && crack_scaled)
1707 v2s32 pos_crack(0, progression*dim_crack.Width);
1708 crack->copyTo(crack_cropped,
1710 core::rect<s32>(pos_crack, dim_crack_cropped));
1711 // Scale crack image by copying
1712 crack_cropped->copyToScaling(crack_scaled);
1713 // Copy or overlay crack image onto each frame
1714 for(s32 i = 0; i < frame_count; ++i)
1716 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1719 blit_with_alpha_overlay(crack_scaled, dst,
1720 v2s32(0,0), dst_pos,
1725 blit_with_alpha(crack_scaled, dst,
1726 v2s32(0,0), dst_pos,
1733 crack_scaled->drop();
1736 crack_cropped->drop();
1739 void brighten(video::IImage *image)
1744 core::dimension2d<u32> dim = image->getDimension();
1746 for(u32 y=0; y<dim.Height; y++)
1747 for(u32 x=0; x<dim.Width; x++)
1749 video::SColor c = image->getPixel(x,y);
1750 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1751 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1752 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1753 image->setPixel(x,y,c);
1757 u32 parseImageTransform(const std::string& s)
1759 int total_transform = 0;
1761 std::string transform_names[8];
1762 transform_names[0] = "i";
1763 transform_names[1] = "r90";
1764 transform_names[2] = "r180";
1765 transform_names[3] = "r270";
1766 transform_names[4] = "fx";
1767 transform_names[6] = "fy";
1769 std::size_t pos = 0;
1770 while(pos < s.size())
1773 for(int i = 0; i <= 7; ++i)
1775 const std::string &name_i = transform_names[i];
1777 if(s[pos] == ('0' + i))
1783 else if(!(name_i.empty()) &&
1784 lowercase(s.substr(pos, name_i.size())) == name_i)
1787 pos += name_i.size();
1794 // Multiply total_transform and transform in the group D4
1797 new_total = (transform + total_transform) % 4;
1799 new_total = (transform - total_transform + 8) % 4;
1800 if((transform >= 4) ^ (total_transform >= 4))
1803 total_transform = new_total;
1805 return total_transform;
1808 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1810 if(transform % 2 == 0)
1813 return core::dimension2d<u32>(dim.Height, dim.Width);
1816 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1818 if(src == NULL || dst == NULL)
1821 core::dimension2d<u32> srcdim = src->getDimension();
1822 core::dimension2d<u32> dstdim = dst->getDimension();
1824 assert(dstdim == imageTransformDimension(transform, srcdim));
1825 assert(transform >= 0 && transform <= 7);
1828 Compute the transformation from source coordinates (sx,sy)
1829 to destination coordinates (dx,dy).
1833 if(transform == 0) // identity
1834 sxn = 0, syn = 2; // sx = dx, sy = dy
1835 else if(transform == 1) // rotate by 90 degrees ccw
1836 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1837 else if(transform == 2) // rotate by 180 degrees
1838 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1839 else if(transform == 3) // rotate by 270 degrees ccw
1840 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1841 else if(transform == 4) // flip x
1842 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1843 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1844 sxn = 2, syn = 0; // sx = dy, sy = dx
1845 else if(transform == 6) // flip y
1846 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1847 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1848 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1850 for(u32 dy=0; dy<dstdim.Height; dy++)
1851 for(u32 dx=0; dx<dstdim.Width; dx++)
1853 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1854 u32 sx = entries[sxn];
1855 u32 sy = entries[syn];
1856 video::SColor c = src->getPixel(sx,sy);
1857 dst->setPixel(dx,dy,c);
1861 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1864 if (isKnownSourceImage("override_normal.png"))
1865 return getTexture("override_normal.png", &id);
1866 std::string fname_base = name;
1867 std::string normal_ext = "_normal.png";
1868 size_t pos = fname_base.find(".");
1869 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1870 if (isKnownSourceImage(fname_normal)) {
1871 // look for image extension and replace it
1873 while ((i = fname_base.find(".", i)) != std::string::npos) {
1874 fname_base.replace(i, 4, normal_ext);
1875 i += normal_ext.length();
1877 return getTexture(fname_base, &id);