3 Copyright (C) 2010-2011 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 General Public License as published by
7 the Free Software Foundation; either version 2 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 General Public License for more details.
15 You should have received a copy of the GNU 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 "main.h" // for g_settings
27 #include <ICameraSceneNode.h>
29 #include "mapnode.h" // For texture atlas making
30 #include "nodedef.h" // For texture atlas making
32 #include "utility_string.h"
35 A cache from texture name to texture path
37 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
40 Replaces the filename extension.
42 std::string image = "a/image.png"
43 replace_ext(image, "jpg")
44 -> image = "a/image.jpg"
45 Returns true on success.
47 static bool replace_ext(std::string &path, const char *ext)
51 // Find place of last dot, fail if \ or / found.
53 for(s32 i=path.size()-1; i>=0; i--)
61 if(path[i] == '\\' || path[i] == '/')
64 // If not found, return an empty string
67 // Else make the new path
68 path = path.substr(0, last_dot_i+1) + ext;
73 Find out the full path of an image by trying different filename
78 static std::string getImagePath(std::string path)
80 // A NULL-ended list of possible image extensions
81 const char *extensions[] = {
82 "png", "jpg", "bmp", "tga",
83 "pcx", "ppm", "psd", "wal", "rgb",
86 // If there is no extension, add one
87 if(removeStringEnd(path, extensions) == "")
89 // Check paths until something is found to exist
90 const char **ext = extensions;
92 bool r = replace_ext(path, *ext);
95 if(fs::PathExists(path))
98 while((++ext) != NULL);
104 Gets the path to a texture by first checking if the texture exists
105 in texture_path and if not, using the data path.
107 Checks all supported extensions by replacing the original extension.
109 If not found, returns "".
111 Utilizes a thread-safe cache.
113 std::string getTexturePath(const std::string &filename)
115 std::string fullpath = "";
119 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
124 Check from texture_path
126 std::string texture_path = g_settings->get("texture_path");
127 if(texture_path != "")
129 std::string testpath = texture_path + DIR_DELIM + filename;
130 // Check all filename extensions. Returns "" if not found.
131 fullpath = getImagePath(testpath);
135 Check from default data directory
139 std::string base_path = porting::path_share + DIR_DELIM + "textures"
140 + DIR_DELIM + "base" + DIR_DELIM + "pack";
141 std::string testpath = base_path + DIR_DELIM + filename;
142 // Check all filename extensions. Returns "" if not found.
143 fullpath = getImagePath(testpath);
146 // Add to cache (also an empty result is cached)
147 g_texturename_to_path_cache.set(filename, fullpath);
154 An internal variant of AtlasPointer with more data.
155 (well, more like a wrapper)
158 struct SourceAtlasPointer
162 video::IImage *atlas_img; // The source image of the atlas
163 // Integer variants of position and size
168 const std::string &name_,
169 AtlasPointer a_=AtlasPointer(0, NULL),
170 video::IImage *atlas_img_=NULL,
171 v2s32 intpos_=v2s32(0,0),
172 v2u32 intsize_=v2u32(0,0)
176 atlas_img(atlas_img_),
184 SourceImageCache: A cache used for storing source images.
187 class SourceImageCache
190 void insert(const std::string &name, video::IImage *img,
191 bool prefer_local, video::IVideoDriver *driver)
195 core::map<std::string, video::IImage*>::Node *n;
196 n = m_images.find(name);
198 video::IImage *oldimg = n->getValue();
202 // Try to use local texture instead if asked to
204 std::string path = getTexturePath(name.c_str());
206 video::IImage *img2 = driver->createImageFromFile(path.c_str());
208 m_images[name] = img2;
214 m_images[name] = img;
216 video::IImage* get(const std::string &name)
218 core::map<std::string, video::IImage*>::Node *n;
219 n = m_images.find(name);
221 return n->getValue();
224 // Primarily fetches from cache, secondarily tries to read from filesystem
225 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
227 core::map<std::string, video::IImage*>::Node *n;
228 n = m_images.find(name);
230 n->getValue()->grab(); // Grab for caller
231 return n->getValue();
233 video::IVideoDriver* driver = device->getVideoDriver();
234 std::string path = getTexturePath(name.c_str());
236 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
237 <<name<<"\""<<std::endl;
240 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
242 video::IImage *img = driver->createImageFromFile(path.c_str());
243 // Even if could not be loaded, put as NULL
244 //m_images[name] = img;
246 m_images[name] = img;
247 img->grab(); // Grab for caller
252 core::map<std::string, video::IImage*> m_images;
259 class TextureSource : public IWritableTextureSource
262 TextureSource(IrrlichtDevice *device);
267 Now, assume a texture with the id 1 exists, and has the name
268 "stone.png^mineral1".
269 Then a random thread calls getTextureId for a texture called
270 "stone.png^mineral1^crack0".
271 ...Now, WTF should happen? Well:
272 - getTextureId strips off stuff recursively from the end until
273 the remaining part is found, or nothing is left when
274 something is stripped out
276 But it is slow to search for textures by names and modify them
278 - ContentFeatures is made to contain ids for the basic plain
280 - Crack textures can be slow by themselves, but the framework
284 - Assume a texture with the id 1 exists, and has the name
285 "stone.png^mineral1" and is specified as a part of some atlas.
286 - Now getNodeTile() stumbles upon a node which uses
287 texture id 1, and determines that MATERIAL_FLAG_CRACK
288 must be applied to the tile
289 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
290 has received the current crack level 0 from the client. It
291 finds out the name of the texture with getTextureName(1),
292 appends "^crack0" to it and gets a new texture id with
293 getTextureId("stone.png^mineral1^crack0").
298 Gets a texture id from cache or
299 - if main thread, from getTextureIdDirect
300 - if other thread, adds to request queue and waits for main thread
302 u32 getTextureId(const std::string &name);
308 "stone.png^mineral_coal.png"
309 "stone.png^mineral_coal.png^crack1"
311 - If texture specified by name is found from cache, return the
313 - Otherwise generate the texture, add to cache and return id.
314 Recursion is used to find out the largest found part of the
315 texture and continue based on it.
317 The id 0 points to a NULL texture. It is returned in case of error.
319 u32 getTextureIdDirect(const std::string &name);
321 // Finds out the name of a cached texture.
322 std::string getTextureName(u32 id);
325 If texture specified by the name pointed by the id doesn't
326 exist, create it, then return the cached texture.
328 Can be called from any thread. If called from some other thread
329 and not found in cache, the call is queued to the main thread
332 AtlasPointer getTexture(u32 id);
334 AtlasPointer getTexture(const std::string &name)
336 return getTexture(getTextureId(name));
339 // Gets a separate texture
340 video::ITexture* getTextureRaw(const std::string &name)
342 AtlasPointer ap = getTexture(name + "^[forcesingle");
346 // Gets a separate texture atlas pointer
347 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
349 return getTexture(getTextureName(ap.id) + "^[forcesingle");
352 // Returns a pointer to the irrlicht device
353 virtual IrrlichtDevice* getDevice()
358 // Update new texture pointer and texture coordinates to an
359 // AtlasPointer based on it's texture id
360 void updateAP(AtlasPointer &ap);
362 // Processes queued texture requests from other threads.
363 // Shall be called from the main thread.
366 // Insert an image into the cache without touching the filesystem.
367 // Shall be called from the main thread.
368 void insertSourceImage(const std::string &name, video::IImage *img);
370 // Rebuild images and textures from the current set of source images
371 // Shall be called from the main thread.
372 void rebuildImagesAndTextures();
374 // Build the main texture atlas which contains most of the
376 void buildMainAtlas(class IGameDef *gamedef);
380 // The id of the thread that is allowed to use irrlicht directly
381 threadid_t m_main_thread;
382 // The irrlicht device
383 IrrlichtDevice *m_device;
385 // Cache of source images
386 // This should be only accessed from the main thread
387 SourceImageCache m_sourcecache;
389 // A texture id is index in this array.
390 // The first position contains a NULL texture.
391 core::array<SourceAtlasPointer> m_atlaspointer_cache;
392 // Maps a texture name to an index in the former.
393 core::map<std::string, u32> m_name_to_id;
394 // The two former containers are behind this mutex
395 JMutex m_atlaspointer_cache_mutex;
397 // Main texture atlas. This is filled at startup and is then not touched.
398 video::IImage *m_main_atlas_image;
399 video::ITexture *m_main_atlas_texture;
401 // Queued texture fetches (to be processed by the main thread)
402 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
405 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
407 return new TextureSource(device);
410 TextureSource::TextureSource(IrrlichtDevice *device):
412 m_main_atlas_image(NULL),
413 m_main_atlas_texture(NULL)
417 m_atlaspointer_cache_mutex.Init();
419 m_main_thread = get_current_thread_id();
421 // Add a NULL AtlasPointer as the first index, named ""
422 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
423 m_name_to_id[""] = 0;
426 TextureSource::~TextureSource()
430 u32 TextureSource::getTextureId(const std::string &name)
432 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
436 See if texture already exists
438 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
439 core::map<std::string, u32>::Node *n;
440 n = m_name_to_id.find(name);
443 return n->getValue();
450 if(get_current_thread_id() == m_main_thread)
452 return getTextureIdDirect(name);
456 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
458 // We're gonna ask the result to be put into here
459 ResultQueue<std::string, u32, u8, u8> result_queue;
461 // Throw a request in
462 m_get_texture_queue.add(name, 0, 0, &result_queue);
464 infostream<<"Waiting for texture from main thread, name=\""
465 <<name<<"\""<<std::endl;
469 // Wait result for a second
470 GetResult<std::string, u32, u8, u8>
471 result = result_queue.pop_front(1000);
473 // Check that at least something worked OK
474 assert(result.key == name);
478 catch(ItemNotFoundException &e)
480 infostream<<"Waiting for texture timed out."<<std::endl;
485 infostream<<"getTextureId(): Failed"<<std::endl;
490 // Overlay image on top of another image (used for cracks)
491 void overlay(video::IImage *image, video::IImage *overlay);
494 void brighten(video::IImage *image);
495 // Parse a transform name
496 u32 parseImageTransform(const std::string& s);
497 // Apply transform to image dimension
498 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
499 // Apply transform to image data
500 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
503 Generate image based on a string like "stone.png" or "[crack0".
504 if baseimg is NULL, it is created. Otherwise stuff is made on it.
506 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
507 IrrlichtDevice *device, SourceImageCache *sourcecache);
510 Generates an image from a full string like
511 "stone.png^mineral_coal.png^[crack0".
513 This is used by buildMainAtlas().
515 video::IImage* generate_image_from_scratch(std::string name,
516 IrrlichtDevice *device, SourceImageCache *sourcecache);
519 This method generates all the textures
521 u32 TextureSource::getTextureIdDirect(const std::string &name)
523 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
525 // Empty name means texture 0
528 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
533 Calling only allowed from main thread
535 if(get_current_thread_id() != m_main_thread)
537 errorstream<<"TextureSource::getTextureIdDirect() "
538 "called not from main thread"<<std::endl;
543 See if texture already exists
546 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
548 core::map<std::string, u32>::Node *n;
549 n = m_name_to_id.find(name);
552 /*infostream<<"getTextureIdDirect(): \""<<name
553 <<"\" found in cache"<<std::endl;*/
554 return n->getValue();
558 /*infostream<<"getTextureIdDirect(): \""<<name
559 <<"\" NOT found in cache. Creating it."<<std::endl;*/
565 char separator = '^';
568 This is set to the id of the base image.
569 If left 0, there is no base image and a completely new image
572 u32 base_image_id = 0;
574 // Find last meta separator in name
575 s32 last_separator_position = -1;
576 for(s32 i=name.size()-1; i>=0; i--)
578 if(name[i] == separator)
580 last_separator_position = i;
585 If separator was found, construct the base name and make the
586 base image using a recursive call
588 std::string base_image_name;
589 if(last_separator_position != -1)
591 // Construct base name
592 base_image_name = name.substr(0, last_separator_position);
593 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
594 " to get base image of \""<<name<<"\" = \""
595 <<base_image_name<<"\""<<std::endl;*/
596 base_image_id = getTextureIdDirect(base_image_name);
599 //infostream<<"base_image_id="<<base_image_id<<std::endl;
601 video::IVideoDriver* driver = m_device->getVideoDriver();
604 video::ITexture *t = NULL;
607 An image will be built from files and then converted into a texture.
609 video::IImage *baseimg = NULL;
611 // If a base image was found, copy it to baseimg
612 if(base_image_id != 0)
614 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
616 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
618 video::IImage *image = ap.atlas_img;
622 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
623 <<"cache: \""<<base_image_name<<"\""
628 core::dimension2d<u32> dim = ap.intsize;
630 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
632 core::position2d<s32> pos_to(0,0);
633 core::position2d<s32> pos_from = ap.intpos;
637 v2s32(0,0), // position in target
638 core::rect<s32>(pos_from, dim) // from
641 /*infostream<<"getTextureIdDirect(): Loaded \""
642 <<base_image_name<<"\" from image cache"
648 Parse out the last part of the name of the image and act
652 std::string last_part_of_name = name.substr(last_separator_position+1);
653 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
655 // Generate image according to part of name
656 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
658 errorstream<<"getTextureIdDirect(): "
659 "failed to generate \""<<last_part_of_name<<"\""
663 // If no resulting image, print a warning
666 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
667 " create texture \""<<name<<"\""<<std::endl;
672 // Create texture from resulting image
673 t = driver->addTexture(name.c_str(), baseimg);
677 Add texture to caches (add NULL textures too)
680 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
682 u32 id = m_atlaspointer_cache.size();
688 core::dimension2d<u32> baseimg_dim(0,0);
690 baseimg_dim = baseimg->getDimension();
691 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
692 m_atlaspointer_cache.push_back(nap);
693 m_name_to_id.insert(name, id);
695 /*infostream<<"getTextureIdDirect(): "
696 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
701 std::string TextureSource::getTextureName(u32 id)
703 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
705 if(id >= m_atlaspointer_cache.size())
707 errorstream<<"TextureSource::getTextureName(): id="<<id
708 <<" >= m_atlaspointer_cache.size()="
709 <<m_atlaspointer_cache.size()<<std::endl;
713 return m_atlaspointer_cache[id].name;
717 AtlasPointer TextureSource::getTexture(u32 id)
719 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
721 if(id >= m_atlaspointer_cache.size())
722 return AtlasPointer(0, NULL);
724 return m_atlaspointer_cache[id].a;
727 void TextureSource::updateAP(AtlasPointer &ap)
729 AtlasPointer ap2 = getTexture(ap.id);
733 void TextureSource::processQueue()
738 if(m_get_texture_queue.size() > 0)
740 GetRequest<std::string, u32, u8, u8>
741 request = m_get_texture_queue.pop();
743 /*infostream<<"TextureSource::processQueue(): "
744 <<"got texture request with "
745 <<"name=\""<<request.key<<"\""
748 GetResult<std::string, u32, u8, u8>
750 result.key = request.key;
751 result.callers = request.callers;
752 result.item = getTextureIdDirect(request.key);
754 request.dest->push_back(result);
758 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
760 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
762 assert(get_current_thread_id() == m_main_thread);
764 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
767 void TextureSource::rebuildImagesAndTextures()
769 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
771 /*// Oh well... just clear everything, they'll load sometime.
772 m_atlaspointer_cache.clear();
773 m_name_to_id.clear();*/
775 video::IVideoDriver* driver = m_device->getVideoDriver();
777 // Remove source images from textures to disable inheriting textures
778 // from existing textures
779 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
780 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
781 sap->atlas_img->drop();
782 sap->atlas_img = NULL;
786 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
787 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
789 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
790 // Create texture from resulting image
791 video::ITexture *t = NULL;
793 t = driver->addTexture(sap->name.c_str(), img);
797 sap->a.pos = v2f(0,0);
798 sap->a.size = v2f(1,1);
800 sap->atlas_img = img;
801 sap->intpos = v2s32(0,0);
802 sap->intsize = img->getDimension();
806 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
808 assert(gamedef->tsrc() == this);
809 INodeDefManager *ndef = gamedef->ndef();
811 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
813 //return; // Disable (for testing)
815 video::IVideoDriver* driver = m_device->getVideoDriver();
818 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
820 // Create an image of the right size
821 core::dimension2d<u32> atlas_dim(1024,1024);
822 video::IImage *atlas_img =
823 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
825 if(atlas_img == NULL)
827 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
828 "image; not building texture atlas."<<std::endl;
833 Grab list of stuff to include in the texture atlas from the
834 main content features
837 core::map<std::string, bool> sourcelist;
839 for(u16 j=0; j<MAX_CONTENT+1; j++)
841 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
843 const ContentFeatures &f = ndef->get(j);
844 for(u32 i=0; i<6; i++)
846 std::string name = f.tname_tiles[i];
847 sourcelist[name] = true;
851 infostream<<"Creating texture atlas out of textures: ";
852 for(core::map<std::string, bool>::Iterator
853 i = sourcelist.getIterator();
854 i.atEnd() == false; i++)
856 std::string name = i.getNode()->getKey();
857 infostream<<"\""<<name<<"\" ";
859 infostream<<std::endl;
861 // Padding to disallow texture bleeding
864 s32 column_width = 256;
865 s32 column_padding = 16;
868 First pass: generate almost everything
870 core::position2d<s32> pos_in_atlas(0,0);
872 pos_in_atlas.Y = padding;
874 for(core::map<std::string, bool>::Iterator
875 i = sourcelist.getIterator();
876 i.atEnd() == false; i++)
878 std::string name = i.getNode()->getKey();
880 // Generate image by name
881 video::IImage *img2 = generate_image_from_scratch(name, m_device,
885 errorstream<<"TextureSource::buildMainAtlas(): "
886 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
890 core::dimension2d<u32> dim = img2->getDimension();
892 // Don't add to atlas if image is large
893 core::dimension2d<u32> max_size_in_atlas(32,32);
894 if(dim.Width > max_size_in_atlas.Width
895 || dim.Height > max_size_in_atlas.Height)
897 infostream<<"TextureSource::buildMainAtlas(): Not adding "
898 <<"\""<<name<<"\" because image is large"<<std::endl;
902 // Wrap columns and stop making atlas if atlas is full
903 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
905 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
906 errorstream<<"TextureSource::buildMainAtlas(): "
907 <<"Atlas is full, not adding more textures."
911 pos_in_atlas.Y = padding;
912 pos_in_atlas.X += column_width + column_padding;
915 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
916 <<"\" to texture atlas"<<std::endl;*/
918 // Tile it a few times in the X direction
919 u16 xwise_tiling = column_width / dim.Width;
920 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
922 for(u32 j=0; j<xwise_tiling; j++)
924 // Copy the copy to the atlas
925 /*img2->copyToWithAlpha(atlas_img,
926 pos_in_atlas + v2s32(j*dim.Width,0),
927 core::rect<s32>(v2s32(0,0), dim),
928 video::SColor(255,255,255,255),
930 img2->copyTo(atlas_img,
931 pos_in_atlas + v2s32(j*dim.Width,0),
932 core::rect<s32>(v2s32(0,0), dim),
936 // Copy the borders a few times to disallow texture bleeding
937 for(u32 side=0; side<2; side++) // top and bottom
938 for(s32 y0=0; y0<padding; y0++)
939 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
945 dst_y = y0 + pos_in_atlas.Y + dim.Height;
946 src_y = pos_in_atlas.Y + dim.Height - 1;
950 dst_y = -y0 + pos_in_atlas.Y-1;
951 src_y = pos_in_atlas.Y;
953 s32 x = x0 + pos_in_atlas.X;
954 video::SColor c = atlas_img->getPixel(x, src_y);
955 atlas_img->setPixel(x,dst_y,c);
961 Add texture to caches
964 bool reuse_old_id = false;
965 u32 id = m_atlaspointer_cache.size();
966 // Check old id without fetching a texture
967 core::map<std::string, u32>::Node *n;
968 n = m_name_to_id.find(name);
969 // If it exists, we will replace the old definition
973 /*infostream<<"TextureSource::buildMainAtlas(): "
974 <<"Replacing old AtlasPointer"<<std::endl;*/
977 // Create AtlasPointer
979 ap.atlas = NULL; // Set on the second pass
980 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
981 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
982 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
983 (float)dim.Width/(float)atlas_dim.Height);
984 ap.tiled = xwise_tiling;
986 // Create SourceAtlasPointer and add to containers
987 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
989 m_atlaspointer_cache[id] = nap;
991 m_atlaspointer_cache.push_back(nap);
992 m_name_to_id[name] = id;
994 // Increment position
995 pos_in_atlas.Y += dim.Height + padding * 2;
1001 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1005 Second pass: set texture pointer in generated AtlasPointers
1007 for(core::map<std::string, bool>::Iterator
1008 i = sourcelist.getIterator();
1009 i.atEnd() == false; i++)
1011 std::string name = i.getNode()->getKey();
1012 if(m_name_to_id.find(name) == NULL)
1014 u32 id = m_name_to_id[name];
1015 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1016 m_atlaspointer_cache[id].a.atlas = t;
1020 Write image to file so that it can be inspected
1022 /*std::string atlaspath = porting::path_user
1023 + DIR_DELIM + "generated_texture_atlas.png";
1024 infostream<<"Removing and writing texture atlas for inspection to "
1025 <<atlaspath<<std::endl;
1026 fs::RecursiveDelete(atlaspath);
1027 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1030 video::IImage* generate_image_from_scratch(std::string name,
1031 IrrlichtDevice *device, SourceImageCache *sourcecache)
1033 /*infostream<<"generate_image_from_scratch(): "
1034 "\""<<name<<"\""<<std::endl;*/
1036 video::IVideoDriver* driver = device->getVideoDriver();
1043 video::IImage *baseimg = NULL;
1045 char separator = '^';
1047 // Find last meta separator in name
1048 s32 last_separator_position = name.find_last_of(separator);
1049 //if(last_separator_position == std::npos)
1050 // last_separator_position = -1;
1052 /*infostream<<"generate_image_from_scratch(): "
1053 <<"last_separator_position="<<last_separator_position
1057 If separator was found, construct the base name and make the
1058 base image using a recursive call
1060 std::string base_image_name;
1061 if(last_separator_position != -1)
1063 // Construct base name
1064 base_image_name = name.substr(0, last_separator_position);
1065 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1066 " to get base image of \""<<name<<"\" = \""
1067 <<base_image_name<<"\""<<std::endl;*/
1068 baseimg = generate_image_from_scratch(base_image_name, device,
1073 Parse out the last part of the name of the image and act
1077 std::string last_part_of_name = name.substr(last_separator_position+1);
1078 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1080 // Generate image according to part of name
1081 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1083 errorstream<<"generate_image_from_scratch(): "
1084 "failed to generate \""<<last_part_of_name<<"\""
1092 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1093 IrrlichtDevice *device, SourceImageCache *sourcecache)
1095 video::IVideoDriver* driver = device->getVideoDriver();
1098 // Stuff starting with [ are special commands
1099 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1101 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1105 if(part_of_name != ""){
1106 errorstream<<"generate_image(): Could not load image \""
1107 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1108 errorstream<<"generate_image(): Creating a dummy"
1109 <<" image for \""<<part_of_name<<"\""<<std::endl;
1112 // Just create a dummy image
1113 //core::dimension2d<u32> dim(2,2);
1114 core::dimension2d<u32> dim(1,1);
1115 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1117 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1118 image->setPixel(1,0, video::SColor(255,0,255,0));
1119 image->setPixel(0,1, video::SColor(255,0,0,255));
1120 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1121 image->setPixel(0,0, video::SColor(255,myrand()%256,
1122 myrand()%256,myrand()%256));
1123 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1124 myrand()%256,myrand()%256));
1125 image->setPixel(0,1, video::SColor(255,myrand()%256,
1126 myrand()%256,myrand()%256));
1127 image->setPixel(1,1, video::SColor(255,myrand()%256,
1128 myrand()%256,myrand()%256));*/
1131 // If base image is NULL, load as base.
1134 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1136 Copy it this way to get an alpha channel.
1137 Otherwise images with alpha cannot be blitted on
1138 images that don't have alpha in the original file.
1140 core::dimension2d<u32> dim = image->getDimension();
1141 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1142 image->copyTo(baseimg);
1145 // Else blit on base.
1148 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1149 // Size of the copied area
1150 core::dimension2d<u32> dim = image->getDimension();
1151 //core::dimension2d<u32> dim(16,16);
1152 // Position to copy the blitted to in the base image
1153 core::position2d<s32> pos_to(0,0);
1154 // Position to copy the blitted from in the blitted image
1155 core::position2d<s32> pos_from(0,0);
1157 image->copyToWithAlpha(baseimg, pos_to,
1158 core::rect<s32>(pos_from, dim),
1159 video::SColor(255,255,255,255),
1167 // A special texture modification
1169 /*infostream<<"generate_image(): generating special "
1170 <<"modification \""<<part_of_name<<"\""
1174 This is the simplest of all; it just adds stuff to the
1175 name so that a separate texture is created.
1177 It is used to make textures for stuff that doesn't want
1178 to implement getting the texture from a bigger texture
1181 if(part_of_name == "[forcesingle")
1183 // If base image is NULL, create a random color
1186 core::dimension2d<u32> dim(1,1);
1187 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1189 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1190 myrand()%256,myrand()%256));
1195 Adds a cracking texture
1197 else if(part_of_name.substr(0,6) == "[crack")
1201 errorstream<<"generate_image(): baseimg==NULL "
1202 <<"for part_of_name=\""<<part_of_name
1203 <<"\", cancelling."<<std::endl;
1207 // Crack image number and overlay option
1208 s32 progression = 0;
1209 bool use_overlay = false;
1210 if(part_of_name.substr(6,1) == "o")
1212 progression = stoi(part_of_name.substr(7));
1217 progression = stoi(part_of_name.substr(6));
1218 use_overlay = false;
1221 // Size of the base image
1222 core::dimension2d<u32> dim_base = baseimg->getDimension();
1227 It is an image with a number of cracking stages
1230 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1232 if(img_crack && progression >= 0)
1234 // Dimension of original image
1235 core::dimension2d<u32> dim_crack
1236 = img_crack->getDimension();
1237 // Count of crack stages
1238 s32 crack_count = dim_crack.Height / dim_crack.Width;
1239 // Limit progression
1240 if(progression > crack_count-1)
1241 progression = crack_count-1;
1242 // Dimension of a single crack stage
1243 core::dimension2d<u32> dim_crack_cropped(
1247 // Create cropped and scaled crack images
1248 video::IImage *img_crack_cropped = driver->createImage(
1249 video::ECF_A8R8G8B8, dim_crack_cropped);
1250 video::IImage *img_crack_scaled = driver->createImage(
1251 video::ECF_A8R8G8B8, dim_base);
1253 if(img_crack_cropped && img_crack_scaled)
1256 v2s32 pos_crack(0, progression*dim_crack.Width);
1257 img_crack->copyTo(img_crack_cropped,
1259 core::rect<s32>(pos_crack, dim_crack_cropped));
1260 // Scale crack image by copying
1261 img_crack_cropped->copyToScaling(img_crack_scaled);
1262 // Copy or overlay crack image
1265 overlay(baseimg, img_crack_scaled);
1269 img_crack_scaled->copyToWithAlpha(
1272 core::rect<s32>(v2s32(0,0), dim_base),
1273 video::SColor(255,255,255,255));
1277 if(img_crack_scaled)
1278 img_crack_scaled->drop();
1280 if(img_crack_cropped)
1281 img_crack_cropped->drop();
1287 [combine:WxH:X,Y=filename:X,Y=filename2
1288 Creates a bigger texture from an amount of smaller ones
1290 else if(part_of_name.substr(0,8) == "[combine")
1292 Strfnd sf(part_of_name);
1294 u32 w0 = stoi(sf.next("x"));
1295 u32 h0 = stoi(sf.next(":"));
1296 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1297 core::dimension2d<u32> dim(w0,h0);
1298 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1299 while(sf.atend() == false)
1301 u32 x = stoi(sf.next(","));
1302 u32 y = stoi(sf.next("="));
1303 std::string filename = sf.next(":");
1304 infostream<<"Adding \""<<filename
1305 <<"\" to combined ("<<x<<","<<y<<")"
1307 video::IImage *img = sourcecache->getOrLoad(filename, device);
1310 core::dimension2d<u32> dim = img->getDimension();
1311 infostream<<"Size "<<dim.Width
1312 <<"x"<<dim.Height<<std::endl;
1313 core::position2d<s32> pos_base(x, y);
1314 video::IImage *img2 =
1315 driver->createImage(video::ECF_A8R8G8B8, dim);
1318 img2->copyToWithAlpha(baseimg, pos_base,
1319 core::rect<s32>(v2s32(0,0), dim),
1320 video::SColor(255,255,255,255),
1326 infostream<<"img==NULL"<<std::endl;
1333 else if(part_of_name.substr(0,9) == "[brighten")
1337 errorstream<<"generate_image(): baseimg==NULL "
1338 <<"for part_of_name=\""<<part_of_name
1339 <<"\", cancelling."<<std::endl;
1347 Make image completely opaque.
1348 Used for the leaves texture when in old leaves mode, so
1349 that the transparent parts don't look completely black
1350 when simple alpha channel is used for rendering.
1352 else if(part_of_name.substr(0,8) == "[noalpha")
1356 errorstream<<"generate_image(): baseimg==NULL "
1357 <<"for part_of_name=\""<<part_of_name
1358 <<"\", cancelling."<<std::endl;
1362 core::dimension2d<u32> dim = baseimg->getDimension();
1364 // Set alpha to full
1365 for(u32 y=0; y<dim.Height; y++)
1366 for(u32 x=0; x<dim.Width; x++)
1368 video::SColor c = baseimg->getPixel(x,y);
1370 baseimg->setPixel(x,y,c);
1375 Convert one color to transparent.
1377 else if(part_of_name.substr(0,11) == "[makealpha:")
1381 errorstream<<"generate_image(): baseimg==NULL "
1382 <<"for part_of_name=\""<<part_of_name
1383 <<"\", cancelling."<<std::endl;
1387 Strfnd sf(part_of_name.substr(11));
1388 u32 r1 = stoi(sf.next(","));
1389 u32 g1 = stoi(sf.next(","));
1390 u32 b1 = stoi(sf.next(""));
1391 std::string filename = sf.next("");
1393 core::dimension2d<u32> dim = baseimg->getDimension();
1395 /*video::IImage *oldbaseimg = baseimg;
1396 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1397 oldbaseimg->copyTo(baseimg);
1398 oldbaseimg->drop();*/
1400 // Set alpha to full
1401 for(u32 y=0; y<dim.Height; y++)
1402 for(u32 x=0; x<dim.Width; x++)
1404 video::SColor c = baseimg->getPixel(x,y);
1406 u32 g = c.getGreen();
1407 u32 b = c.getBlue();
1408 if(!(r == r1 && g == g1 && b == b1))
1411 baseimg->setPixel(x,y,c);
1416 Rotates and/or flips the image.
1418 N can be a number (between 0 and 7) or a transform name.
1419 Rotations are counter-clockwise.
1421 1 R90 rotate by 90 degrees
1422 2 R180 rotate by 180 degrees
1423 3 R270 rotate by 270 degrees
1425 5 FXR90 flip X then rotate by 90 degrees
1427 7 FYR90 flip Y then rotate by 90 degrees
1429 Note: Transform names can be concatenated to produce
1430 their product (applies the first then the second).
1431 The resulting transform will be equivalent to one of the
1432 eight existing ones, though (see: dihedral group).
1434 else if(part_of_name.substr(0,10) == "[transform")
1438 errorstream<<"generate_image(): baseimg==NULL "
1439 <<"for part_of_name=\""<<part_of_name
1440 <<"\", cancelling."<<std::endl;
1444 u32 transform = parseImageTransform(part_of_name.substr(10));
1445 core::dimension2d<u32> dim = imageTransformDimension(
1446 transform, baseimg->getDimension());
1447 video::IImage *image = driver->createImage(
1448 baseimg->getColorFormat(), dim);
1450 imageTransform(transform, baseimg, image);
1455 [inventorycube{topimage{leftimage{rightimage
1456 In every subimage, replace ^ with &.
1457 Create an "inventory cube".
1458 NOTE: This should be used only on its own.
1459 Example (a grass block (not actually used in game):
1460 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1462 else if(part_of_name.substr(0,14) == "[inventorycube")
1466 errorstream<<"generate_image(): baseimg!=NULL "
1467 <<"for part_of_name=\""<<part_of_name
1468 <<"\", cancelling."<<std::endl;
1472 str_replace_char(part_of_name, '&', '^');
1473 Strfnd sf(part_of_name);
1475 std::string imagename_top = sf.next("{");
1476 std::string imagename_left = sf.next("{");
1477 std::string imagename_right = sf.next("{");
1479 // Generate images for the faces of the cube
1480 video::IImage *img_top = generate_image_from_scratch(
1481 imagename_top, device, sourcecache);
1482 video::IImage *img_left = generate_image_from_scratch(
1483 imagename_left, device, sourcecache);
1484 video::IImage *img_right = generate_image_from_scratch(
1485 imagename_right, device, sourcecache);
1486 assert(img_top && img_left && img_right);
1488 // Create textures from images
1489 video::ITexture *texture_top = driver->addTexture(
1490 (imagename_top + "__temp__").c_str(), img_top);
1491 video::ITexture *texture_left = driver->addTexture(
1492 (imagename_left + "__temp__").c_str(), img_left);
1493 video::ITexture *texture_right = driver->addTexture(
1494 (imagename_right + "__temp__").c_str(), img_right);
1495 assert(texture_top && texture_left && texture_right);
1503 Draw a cube mesh into a render target texture
1505 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1506 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1507 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1508 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1509 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1510 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1511 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1512 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1514 core::dimension2d<u32> dim(64,64);
1515 std::string rtt_texture_name = part_of_name + "_RTT";
1517 v3f camera_position(0, 1.0, -1.5);
1518 camera_position.rotateXZBy(45);
1519 v3f camera_lookat(0, 0, 0);
1520 core::CMatrix4<f32> camera_projection_matrix;
1521 // Set orthogonal projection
1522 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1523 1.65, 1.65, 0, 100);
1525 video::SColorf ambient_light(0.2,0.2,0.2);
1526 v3f light_position(10, 100, -50);
1527 video::SColorf light_color(0.5,0.5,0.5);
1528 f32 light_radius = 1000;
1530 video::ITexture *rtt = generateTextureFromMesh(
1531 cube, device, dim, rtt_texture_name,
1534 camera_projection_matrix,
1543 // Free textures of images
1544 driver->removeTexture(texture_top);
1545 driver->removeTexture(texture_left);
1546 driver->removeTexture(texture_right);
1550 baseimg = generate_image_from_scratch(
1551 imagename_top, device, sourcecache);
1555 // Create image of render target
1556 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1559 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1563 image->copyTo(baseimg);
1569 errorstream<<"generate_image(): Invalid "
1570 " modification: \""<<part_of_name<<"\""<<std::endl;
1577 void overlay(video::IImage *image, video::IImage *overlay)
1580 Copy overlay to image, taking alpha into account.
1581 Where image is transparent, don't copy from overlay.
1582 Images sizes must be identical.
1584 if(image == NULL || overlay == NULL)
1587 core::dimension2d<u32> dim = image->getDimension();
1588 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1589 assert(dim == dim_overlay);
1591 for(u32 y=0; y<dim.Height; y++)
1592 for(u32 x=0; x<dim.Width; x++)
1594 video::SColor c1 = image->getPixel(x,y);
1595 video::SColor c2 = overlay->getPixel(x,y);
1596 u32 a1 = c1.getAlpha();
1597 u32 a2 = c2.getAlpha();
1598 if(a1 == 255 && a2 != 0)
1600 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1601 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1602 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1604 image->setPixel(x,y,c1);
1608 void brighten(video::IImage *image)
1613 core::dimension2d<u32> dim = image->getDimension();
1615 for(u32 y=0; y<dim.Height; y++)
1616 for(u32 x=0; x<dim.Width; x++)
1618 video::SColor c = image->getPixel(x,y);
1619 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1620 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1621 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1622 image->setPixel(x,y,c);
1626 u32 parseImageTransform(const std::string& s)
1628 int total_transform = 0;
1630 std::string transform_names[8];
1631 transform_names[0] = "i";
1632 transform_names[1] = "r90";
1633 transform_names[2] = "r180";
1634 transform_names[3] = "r270";
1635 transform_names[4] = "fx";
1636 transform_names[6] = "fy";
1638 std::size_t pos = 0;
1639 while(pos < s.size())
1642 for(int i = 0; i <= 7; ++i)
1644 const std::string &name_i = transform_names[i];
1646 if(s[pos] == ('0' + i))
1652 else if(!(name_i.empty()) &&
1653 lowercase(s.substr(pos, name_i.size())) == name_i)
1656 pos += name_i.size();
1663 // Multiply total_transform and transform in the group D4
1666 new_total = (transform + total_transform) % 4;
1668 new_total = (transform - total_transform + 8) % 4;
1669 if((transform >= 4) ^ (total_transform >= 4))
1672 total_transform = new_total;
1674 return total_transform;
1677 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1679 if(transform % 2 == 0)
1682 return core::dimension2d<u32>(dim.Height, dim.Width);
1685 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1687 if(src == NULL || dst == NULL)
1690 core::dimension2d<u32> srcdim = src->getDimension();
1691 core::dimension2d<u32> dstdim = dst->getDimension();
1693 assert(dstdim == imageTransformDimension(transform, srcdim));
1694 assert(transform >= 0 && transform <= 7);
1697 Compute the transformation from source coordinates (sx,sy)
1698 to destination coordinates (dx,dy).
1702 if(transform == 0) // identity
1703 sxn = 0, syn = 2; // sx = dx, sy = dy
1704 else if(transform == 1) // rotate by 90 degrees ccw
1705 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1706 else if(transform == 2) // rotate by 180 degrees
1707 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1708 else if(transform == 3) // rotate by 270 degrees ccw
1709 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1710 else if(transform == 4) // flip x
1711 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1712 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1713 sxn = 2, syn = 0; // sx = dy, sy = dx
1714 else if(transform == 6) // flip y
1715 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1716 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1717 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1719 for(u32 dy=0; dy<dstdim.Height; dy++)
1720 for(u32 dx=0; dx<dstdim.Width; dx++)
1722 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1723 u32 sx = entries[sxn];
1724 u32 sy = entries[syn];
1725 video::SColor c = src->getPixel(sx,sy);
1726 dst->setPixel(dx,dy,c);