X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Ftile.cpp;h=17ec51614fe1265eaf8f2cf72762c875d6060004;hb=37b7f094e3ea502339794f64e8bad22444c6fb54;hp=9497c4ca9ac2e8b6a3c1d4c009bad35c02a0906b;hpb=1b078efd5fe3a80011339f90df06e5f55fdbadf3;p=oweals%2Fminetest.git diff --git a/src/tile.cpp b/src/tile.cpp index 9497c4ca9..17ec51614 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,35 +1,40 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License along +You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tile.h" +#include "irrlichttypes_extrabloated.h" #include "debug.h" #include "main.h" // for g_settings #include "filesys.h" -#include "utility.h" #include "settings.h" #include "mesh.h" #include #include "log.h" -#include "mapnode.h" // For texture atlas making -#include "nodedef.h" // For texture atlas making #include "gamedef.h" -#include "utility_string.h" +#include "util/string.h" +#include "util/container.h" +#include "util/thread.h" +#include "util/numeric.h" + +#ifdef __ANDROID__ +#include +#endif /* A cache from texture name to texture path @@ -57,7 +62,7 @@ static bool replace_ext(std::string &path, const char *ext) last_dot_i = i; break; } - + if(path[i] == '\\' || path[i] == '/') break; } @@ -75,7 +80,7 @@ static bool replace_ext(std::string &path, const char *ext) If failed, return "". */ -static std::string getImagePath(std::string path) +std::string getImagePath(std::string path) { // A NULL-ended list of possible image extensions const char *extensions[] = { @@ -96,7 +101,7 @@ static std::string getImagePath(std::string path) return path; } while((++ext) != NULL); - + return ""; } @@ -119,7 +124,7 @@ std::string getTexturePath(const std::string &filename) bool incache = g_texturename_to_path_cache.get(filename, &fullpath); if(incache) return fullpath; - + /* Check from texture_path */ @@ -130,18 +135,6 @@ std::string getTexturePath(const std::string &filename) // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); } - - /* - Check from $user/textures/all - */ - if(fullpath == "") - { - std::string texture_path = porting::path_user + DIR_DELIM - + "textures" + DIR_DELIM + "all"; - std::string testpath = texture_path + DIR_DELIM + filename; - // Check all filename extensions. Returns "" if not found. - fullpath = getImagePath(testpath); - } /* Check from default data directory @@ -154,40 +147,34 @@ std::string getTexturePath(const std::string &filename) // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); } - + // Add to cache (also an empty result is cached) g_texturename_to_path_cache.set(filename, fullpath); - + // Finally return it return fullpath; } +void clearTextureNameCache() +{ + g_texturename_to_path_cache.clear(); +} + /* - An internal variant of AtlasPointer with more data. - (well, more like a wrapper) + Stores internal information about a texture. */ -struct SourceAtlasPointer +struct TextureInfo { std::string name; - AtlasPointer a; - video::IImage *atlas_img; // The source image of the atlas - // Integer variants of position and size - v2s32 intpos; - v2u32 intsize; + video::ITexture *texture; - SourceAtlasPointer( + TextureInfo( const std::string &name_, - AtlasPointer a_=AtlasPointer(0, NULL), - video::IImage *atlas_img_=NULL, - v2s32 intpos_=v2s32(0,0), - v2u32 intsize_=v2u32(0,0) + video::ITexture *texture_=NULL ): name(name_), - a(a_), - atlas_img(atlas_img_), - intpos(intpos_), - intsize(intsize_) + texture(texture_) { } }; @@ -199,48 +186,60 @@ struct SourceAtlasPointer class SourceImageCache { public: + ~SourceImageCache() { + for(std::map::iterator iter = m_images.begin(); + iter != m_images.end(); iter++) { + iter->second->drop(); + } + m_images.clear(); + } void insert(const std::string &name, video::IImage *img, bool prefer_local, video::IVideoDriver *driver) { assert(img); // Remove old image - core::map::Node *n; + std::map::iterator n; n = m_images.find(name); - if(n){ - video::IImage *oldimg = n->getValue(); - if(oldimg) - oldimg->drop(); + if(n != m_images.end()){ + if(n->second) + n->second->drop(); } + + video::IImage* toadd = img; + bool need_to_grab = true; + // Try to use local texture instead if asked to if(prefer_local){ std::string path = getTexturePath(name.c_str()); if(path != ""){ video::IImage *img2 = driver->createImageFromFile(path.c_str()); if(img2){ - m_images[name] = img2; - return; + toadd = img2; + need_to_grab = false; } } } - img->grab(); - m_images[name] = img; + + if (need_to_grab) + toadd->grab(); + m_images[name] = toadd; } video::IImage* get(const std::string &name) { - core::map::Node *n; + std::map::iterator n; n = m_images.find(name); - if(n) - return n->getValue(); + if(n != m_images.end()) + return n->second; return NULL; } // Primarily fetches from cache, secondarily tries to read from filesystem video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device) { - core::map::Node *n; + std::map::iterator n; n = m_images.find(name); - if(n){ - n->getValue()->grab(); // Grab for caller - return n->getValue(); + if(n != m_images.end()){ + n->second->grab(); // Grab for caller + return n->second; } video::IVideoDriver* driver = device->getVideoDriver(); std::string path = getTexturePath(name.c_str()); @@ -252,8 +251,7 @@ public: infostream<<"SourceImageCache::getOrLoad(): Loading path \""<createImageFromFile(path.c_str()); - // Even if could not be loaded, put as NULL - //m_images[name] = img; + if(img){ m_images[name] = img; img->grab(); // Grab for caller @@ -261,7 +259,7 @@ public: return img; } private: - core::map m_images; + std::map m_images; }; /* @@ -272,7 +270,7 @@ class TextureSource : public IWritableTextureSource { public: TextureSource(IrrlichtDevice *device); - ~TextureSource(); + virtual ~TextureSource(); /* Example case: @@ -294,7 +292,7 @@ public: Example case #2: - Assume a texture with the id 1 exists, and has the name - "stone.png^mineral1" and is specified as a part of some atlas. + "stone.png^mineral_coal.png". - Now getNodeTile() stumbles upon a node which uses texture id 1, and determines that MATERIAL_FLAG_CRACK must be applied to the tile @@ -302,17 +300,17 @@ public: has received the current crack level 0 from the client. It finds out the name of the texture with getTextureName(1), appends "^crack0" to it and gets a new texture id with - getTextureId("stone.png^mineral1^crack0"). + getTextureId("stone.png^mineral_coal.png^crack0"). */ - + /* Gets a texture id from cache or - if main thread, from getTextureIdDirect - if other thread, adds to request queue and waits for main thread */ u32 getTextureId(const std::string &name); - + /* Example names: "stone.png" @@ -341,25 +339,9 @@ public: and not found in cache, the call is queued to the main thread for processing. */ - AtlasPointer getTexture(u32 id); - - AtlasPointer getTexture(const std::string &name) - { - return getTexture(getTextureId(name)); - } - - // Gets a separate texture - video::ITexture* getTextureRaw(const std::string &name) - { - AtlasPointer ap = getTexture(name + "^[forcesingle"); - return ap.atlas; - } + video::ITexture* getTexture(u32 id); - // Gets a separate texture atlas pointer - AtlasPointer getTextureRawAP(const AtlasPointer &ap) - { - return getTexture(getTextureName(ap.id) + "^[forcesingle"); - } + video::ITexture* getTexture(const std::string &name, u32 *id); // Returns a pointer to the irrlicht device virtual IrrlichtDevice* getDevice() @@ -367,51 +349,79 @@ public: return m_device; } - // Update new texture pointer and texture coordinates to an - // AtlasPointer based on it's texture id - void updateAP(AtlasPointer &ap); + bool isKnownSourceImage(const std::string &name) + { + bool is_known = false; + bool cache_found = m_source_image_existence.get(name, &is_known); + if(cache_found) + return is_known; + // Not found in cache; find out if a local file exists + is_known = (getTexturePath(name) != ""); + m_source_image_existence.set(name, is_known); + return is_known; + } // Processes queued texture requests from other threads. // Shall be called from the main thread. void processQueue(); - + // Insert an image into the cache without touching the filesystem. // Shall be called from the main thread. void insertSourceImage(const std::string &name, video::IImage *img); - + // Rebuild images and textures from the current set of source images // Shall be called from the main thread. void rebuildImagesAndTextures(); - // Build the main texture atlas which contains most of the - // textures. - void buildMainAtlas(class IGameDef *gamedef); - + // Render a mesh to a texture. + // Returns NULL if render-to-texture failed. + // Shall be called from the main thread. + video::ITexture* generateTextureFromMesh( + const TextureFromMeshParams ¶ms); + + // Generates an image from a full string like + // "stone.png^mineral_coal.png^[crack:1:0". + // Shall be called from the main thread. + video::IImage* generateImageFromScratch(std::string name); + + // Generate image based on a string like "stone.png" or "[crack:1:0". + // if baseimg is NULL, it is created. Otherwise stuff is made on it. + // Shall be called from the main thread. + bool generateImage(std::string part_of_name, video::IImage *& baseimg); + private: - + // The id of the thread that is allowed to use irrlicht directly threadid_t m_main_thread; // The irrlicht device IrrlichtDevice *m_device; - + // Cache of source images // This should be only accessed from the main thread SourceImageCache m_sourcecache; + // Thread-safe cache of what source images are known (true = known) + MutexedMap m_source_image_existence; + // A texture id is index in this array. // The first position contains a NULL texture. - core::array m_atlaspointer_cache; + std::vector m_textureinfo_cache; // Maps a texture name to an index in the former. - core::map m_name_to_id; + std::map m_name_to_id; // The two former containers are behind this mutex - JMutex m_atlaspointer_cache_mutex; - - // Main texture atlas. This is filled at startup and is then not touched. - video::IImage *m_main_atlas_image; - video::ITexture *m_main_atlas_texture; + JMutex m_textureinfo_cache_mutex; // Queued texture fetches (to be processed by the main thread) RequestQueue m_get_texture_queue; + + // Textures that have been overwritten with other ones + // but can't be deleted because the ITexture* might still be used + std::list m_texture_trash; + + // Cached settings needed for making textures from meshes + bool m_setting_trilinear_filter; + bool m_setting_bilinear_filter; + bool m_setting_anisotropic_filter; }; IWritableTextureSource* createTextureSource(IrrlichtDevice *device) @@ -420,23 +430,52 @@ IWritableTextureSource* createTextureSource(IrrlichtDevice *device) } TextureSource::TextureSource(IrrlichtDevice *device): - m_device(device), - m_main_atlas_image(NULL), - m_main_atlas_texture(NULL) + m_device(device) { assert(m_device); - - m_atlaspointer_cache_mutex.Init(); - + m_main_thread = get_current_thread_id(); - - // Add a NULL AtlasPointer as the first index, named "" - m_atlaspointer_cache.push_back(SourceAtlasPointer("")); + + // Add a NULL TextureInfo as the first index, named "" + m_textureinfo_cache.push_back(TextureInfo("")); m_name_to_id[""] = 0; + + // Cache some settings + // Note: Since this is only done once, the game must be restarted + // for these settings to take effect + m_setting_trilinear_filter = g_settings->getBool("trilinear_filter"); + m_setting_bilinear_filter = g_settings->getBool("bilinear_filter"); + m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter"); } TextureSource::~TextureSource() { + video::IVideoDriver* driver = m_device->getVideoDriver(); + + unsigned int textures_before = driver->getTextureCount(); + + for (std::vector::iterator iter = + m_textureinfo_cache.begin(); + iter != m_textureinfo_cache.end(); iter++) + { + //cleanup texture + if (iter->texture) + driver->removeTexture(iter->texture); + } + m_textureinfo_cache.clear(); + + for (std::list::iterator iter = + m_texture_trash.begin(); iter != m_texture_trash.end(); + iter++) + { + video::ITexture *t = *iter; + + //cleanup trashed texture + driver->removeTexture(t); + } + + infostream << "~TextureSource() "<< textures_before << "/" + << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) @@ -447,15 +486,15 @@ u32 TextureSource::getTextureId(const std::string &name) /* See if texture already exists */ - JMutexAutoLock lock(m_atlaspointer_cache_mutex); - core::map::Node *n; + JMutexAutoLock lock(m_textureinfo_cache_mutex); + std::map::iterator n; n = m_name_to_id.find(name); - if(n != NULL) + if(n != m_name_to_id.end()) { - return n->getValue(); + return n->second; } } - + /* Get texture */ @@ -468,39 +507,52 @@ u32 TextureSource::getTextureId(const std::string &name) infostream<<"getTextureId(): Queued: name=\""< result_queue; - + static ResultQueue result_queue; + // Throw a request in m_get_texture_queue.add(name, 0, 0, &result_queue); - - infostream<<"Waiting for texture from main thread, name=\"" - < + while(true) { + // Wait result for a second + GetResult result = result_queue.pop_front(1000); - - // Check that at least something worked OK - assert(result.key == name); - return result.item; + if (result.key == name) { + return result.item; + } + } } catch(ItemNotFoundException &e) { - infostream<<"Waiting for texture timed out."< imageTransformDimension(u32 transform, core::dimension2d< // Apply transform to image data void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); -/* - Generate image based on a string like "stone.png" or "[crack0". - if baseimg is NULL, it is created. Otherwise stuff is made on it. -*/ -bool generate_image(std::string part_of_name, video::IImage *& baseimg, - IrrlichtDevice *device, SourceImageCache *sourcecache); - -/* - Generates an image from a full string like - "stone.png^mineral_coal.png^[crack0". - - This is used by buildMainAtlas(). -*/ -video::IImage* generate_image_from_scratch(std::string name, - IrrlichtDevice *device, SourceImageCache *sourcecache); - /* This method generates all the textures */ @@ -540,7 +576,7 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) infostream<<"getTextureIdDirect(): name is empty"<::Node *n; + std::map::iterator n; n = m_name_to_id.find(name); - if(n != NULL) + if(n != m_name_to_id.end()) { /*infostream<<"getTextureIdDirect(): \""<getValue(); + return n->second; } } /*infostream<<"getTextureIdDirect(): \""<=0; i--) @@ -607,9 +643,9 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) < dim = ap.intsize; + core::dimension2d dim = ti->texture->getSize(); - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - - core::position2d pos_to(0,0); - core::position2d pos_from = ap.intpos; - - image->copyTo( - baseimg, // target - v2s32(0,0), // position in target - core::rect(pos_from, dim) // from - ); + baseimg = driver->createImage(ti->texture,v2s32(0,0), dim); /*infostream<<"getTextureIdDirect(): Loaded \"" <addTexture(name.c_str(), baseimg); + baseimg->drop(); } - + /* Add texture to caches (add NULL textures too) */ - JMutexAutoLock lock(m_atlaspointer_cache_mutex); - - u32 id = m_atlaspointer_cache.size(); - AtlasPointer ap(id); - ap.atlas = t; - ap.pos = v2f(0,0); - ap.size = v2f(1,1); - ap.tiled = 0; - core::dimension2d baseimg_dim(0,0); - if(baseimg) - baseimg_dim = baseimg->getDimension(); - SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim); - m_atlaspointer_cache.push_back(nap); - m_name_to_id.insert(name, id); - - /*infostream<<"getTextureIdDirect(): " - <<"Returning id="<= m_atlaspointer_cache.size()) + if(id >= m_textureinfo_cache.size()) { errorstream<<"TextureSource::getTextureName(): id="<= m_atlaspointer_cache.size()=" - <= m_textureinfo_cache.size()=" + <= m_textureinfo_cache.size()) + return NULL; - if(id >= m_atlaspointer_cache.size()) - return AtlasPointer(0, NULL); - - return m_atlaspointer_cache[id].a; + return m_textureinfo_cache[id].texture; } -void TextureSource::updateAP(AtlasPointer &ap) +video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) { - AtlasPointer ap2 = getTexture(ap.id); - ap = ap2; + u32 actual_id = getTextureId(name); + if(id){ + *id = actual_id; + } + return getTexture(actual_id); } void TextureSource::processQueue() @@ -747,7 +767,8 @@ void TextureSource::processQueue() /* Fetch textures */ - if(m_get_texture_queue.size() > 0) + //NOTE this is only thread safe for ONE consumer thread! + if(!m_get_texture_queue.empty()) { GetRequest request = m_get_texture_queue.pop(); @@ -757,322 +778,257 @@ void TextureSource::processQueue() <<"name=\""< - result; - result.key = request.key; - result.callers = request.callers; - result.item = getTextureIdDirect(request.key); - - request.dest->push_back(result); + m_get_texture_queue.pushResult(request,getTextureIdDirect(request.key)); } } void TextureSource::insertSourceImage(const std::string &name, video::IImage *img) { //infostream<<"TextureSource::insertSourceImage(): name="<getVideoDriver()); + m_source_image_existence.set(name, true); } - + void TextureSource::rebuildImagesAndTextures() { - JMutexAutoLock lock(m_atlaspointer_cache_mutex); - - /*// Oh well... just clear everything, they'll load sometime. - m_atlaspointer_cache.clear(); - m_name_to_id.clear();*/ + JMutexAutoLock lock(m_textureinfo_cache_mutex); video::IVideoDriver* driver = m_device->getVideoDriver(); - - // Remove source images from textures to disable inheriting textures - // from existing textures - /*for(u32 i=0; iatlas_img->drop(); - sap->atlas_img = NULL; - }*/ - + assert(driver != 0); + // Recreate textures - for(u32 i=0; iname, m_device, &m_sourcecache); + for(u32 i=0; iname); +#ifdef __ANDROID__ + img = Align2Npot2(img,driver); + assert(img->getDimension().Height == npot2(img->getDimension().Height)); + assert(img->getDimension().Width == npot2(img->getDimension().Width)); +#endif // Create texture from resulting image video::ITexture *t = NULL; - if(img) - t = driver->addTexture(sap->name.c_str(), img); - + if(img) { + t = driver->addTexture(ti->name.c_str(), img); + img->drop(); + } + video::ITexture *t_old = ti->texture; // Replace texture - sap->a.atlas = t; - sap->a.pos = v2f(0,0); - sap->a.size = v2f(1,1); - sap->a.tiled = 0; - sap->atlas_img = img; - sap->intpos = v2s32(0,0); - sap->intsize = img->getDimension(); + ti->texture = t; + + if (t_old != 0) + m_texture_trash.push_back(t_old); } } -void TextureSource::buildMainAtlas(class IGameDef *gamedef) +video::ITexture* TextureSource::generateTextureFromMesh( + const TextureFromMeshParams ¶ms) { - assert(gamedef->tsrc() == this); - INodeDefManager *ndef = gamedef->ndef(); - - infostream<<"TextureSource::buildMainAtlas()"<getVideoDriver(); + video::IVideoDriver *driver = m_device->getVideoDriver(); assert(driver); - JMutexAutoLock lock(m_atlaspointer_cache_mutex); - - // Create an image of the right size - core::dimension2d max_dim = driver->getMaxTextureSize(); - core::dimension2d atlas_dim(2048,2048); - atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width); - atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height); - video::IImage *atlas_img = - driver->createImage(video::ECF_A8R8G8B8, atlas_dim); - //assert(atlas_img); - if(atlas_img == NULL) - { - errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas " - "image; not building texture atlas."<getBool("inventory_image_hack") + ) { + // Get a scene manager + scene::ISceneManager *smgr_main = m_device->getSceneManager(); + assert(smgr_main); + scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); + assert(smgr); + + const float scaling = 0.2; + + scene::IMeshSceneNode* meshnode = + smgr->addMeshSceneNode(params.mesh, NULL, + -1, v3f(0,0,0), v3f(0,0,0), + v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true); + meshnode->setMaterialFlag(video::EMF_LIGHTING, true); + meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); + meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); + meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); + meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); + + scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, + params.camera_position, params.camera_lookat); + // second parameter of setProjectionMatrix (isOrthogonal) is ignored + camera->setProjectionMatrix(params.camera_projection_matrix, false); + + smgr->setAmbientLight(params.ambient_light); + smgr->addLightSceneNode(0, + params.light_position, + params.light_color, + params.light_radius*scaling); + + core::dimension2d screen = driver->getScreenSize(); + + // Render scene + driver->beginScene(true, true, video::SColor(0,0,0,0)); + driver->clearZBuffer(); + smgr->drawAll(); + + core::dimension2d partsize(screen.Width * scaling,screen.Height * scaling); + + irr::video::IImage* rawImage = + driver->createImage(irr::video::ECF_A8R8G8B8, partsize); + + u8* pixels = static_cast(rawImage->lock()); + if (!pixels) + { + rawImage->drop(); + return NULL; + } - /* - Grab list of stuff to include in the texture atlas from the - main content features - */ + core::rect source( + screen.Width /2 - (screen.Width * (scaling / 2)), + screen.Height/2 - (screen.Height * (scaling / 2)), + screen.Width /2 + (screen.Width * (scaling / 2)), + screen.Height/2 + (screen.Height * (scaling / 2)) + ); - core::map sourcelist; + glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y, + partsize.Width, partsize.Height, GL_RGBA, + GL_UNSIGNED_BYTE, pixels); - for(u16 j=0; jget(j); - for(u32 i=0; i<6; i++) - { - std::string name = f.tname_tiles[i]; - sourcelist[name] = true; - } - } - - infostream<<"Creating texture atlas out of textures: "; - for(core::map::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) - { - std::string name = i.getNode()->getKey(); - infostream<<"\""<endScene(); - // Padding to disallow texture bleeding - // (16 needed if mipmapping is used; otherwise less will work too) - s32 padding = 16; - s32 column_padding = 16; - s32 column_width = 256; // Space for 16 pieces of 16x16 textures + // Drop scene manager + smgr->drop(); - /* - First pass: generate almost everything - */ - core::position2d pos_in_atlas(0,0); - - pos_in_atlas.X = column_padding; - pos_in_atlas.Y = padding; - - for(core::map::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) - { - std::string name = i.getNode()->getKey(); + unsigned int pixelcount = partsize.Width*partsize.Height; - // Generate image by name - video::IImage *img2 = generate_image_from_scratch(name, m_device, - &m_sourcecache); - if(img2 == NULL) - { - errorstream<<"TextureSource::buildMainAtlas(): " - <<"Couldn't generate image \""< dim = img2->getDimension(); + u8 B = *runptr; + u8 G = *(runptr+1); + u8 R = *(runptr+2); + u8 A = *(runptr+3); - // Don't add to atlas if image is too large - core::dimension2d max_size_in_atlas(64,64); - if(dim.Width > max_size_in_atlas.Width - || dim.Height > max_size_in_atlas.Height) - { - infostream<<"TextureSource::buildMainAtlas(): Not adding " - <<"\""< RGBA + *runptr = R; + runptr ++; + *runptr = G; + runptr ++; + *runptr = B; + runptr ++; + *runptr = A; + runptr ++; } - // Wrap columns and stop making atlas if atlas is full - if(pos_in_atlas.Y + dim.Height > atlas_dim.Height) - { - if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){ - errorstream<<"TextureSource::buildMainAtlas(): " - <<"Atlas is full, not adding more textures." - < 16) // Limit to 16 (more gives no benefit) - xwise_tiling = 16; - for(u32 j=0; jcopyToWithAlpha(atlas_img, - pos_in_atlas + v2s32(j*dim.Width,0), - core::rect(v2s32(0,0), dim), - video::SColor(255,255,255,255), - NULL);*/ - img2->copyTo(atlas_img, - pos_in_atlas + v2s32(j*dim.Width,0), - core::rect(v2s32(0,0), dim), - NULL); - } + video::IImage* inventory_image = + driver->createImage(irr::video::ECF_A8R8G8B8, params.dim); - // Copy the borders a few times to disallow texture bleeding - for(u32 side=0; side<2; side++) // top and bottom - for(s32 y0=0; y0getPixel(x, src_y); - atlas_img->setPixel(x,dst_y,c); - } + rawImage->copyToScaling(inventory_image); + rawImage->drop(); - for(u32 side=0; side<2; side++) // left and right - for(s32 x0=0; x0getPixel(src_x, src_y); - atlas_img->setPixel(dst_x,dst_y,c); + video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); + inventory_image->drop(); + + if (rtt == NULL) { + errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl; + return NULL; } - img2->drop(); + driver->makeColorKeyTexture(rtt, v2s32(0,0)); - /* - Add texture to caches - */ - - bool reuse_old_id = false; - u32 id = m_atlaspointer_cache.size(); - // Check old id without fetching a texture - core::map::Node *n; - n = m_name_to_id.find(name); - // If it exists, we will replace the old definition - if(n){ - id = n->getValue(); - reuse_old_id = true; - /*infostream<<"TextureSource::buildMainAtlas(): " - <<"Replacing old AtlasPointer"<addTexture("__main_atlas__", atlas_img); - assert(t); + if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false) + { + static bool warned = false; + if(!warned) + { + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"EVDF_RENDER_TO_TARGET not supported."<::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) + // Create render target texture + video::ITexture *rtt = driver->addRenderTargetTexture( + params.dim, params.rtt_texture_name.c_str(), + video::ECF_A8R8G8B8); + if(rtt == NULL) { - std::string name = i.getNode()->getKey(); - if(m_name_to_id.find(name) == NULL) - continue; - u32 id = m_name_to_id[name]; - //infostream<<"id of name "<writeImageToFile(atlas_img, atlaspath.c_str());*/ + // Set render target + if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) { + driver->removeTexture(rtt); + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"failed to set render target"<getSceneManager(); + assert(smgr_main); + scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); + assert(smgr); + + scene::IMeshSceneNode* meshnode = + smgr->addMeshSceneNode(params.mesh, NULL, + -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true); + meshnode->setMaterialFlag(video::EMF_LIGHTING, true); + meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); + meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); + meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); + meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); + + scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, + params.camera_position, params.camera_lookat); + // second parameter of setProjectionMatrix (isOrthogonal) is ignored + camera->setProjectionMatrix(params.camera_projection_matrix, false); + + smgr->setAmbientLight(params.ambient_light); + smgr->addLightSceneNode(0, + params.light_position, + params.light_color, + params.light_radius); + + // Render scene + driver->beginScene(true, true, video::SColor(0,0,0,0)); + smgr->drawAll(); + driver->endScene(); + + // Drop scene manager + smgr->drop(); + + // Unset render target + driver->setRenderTarget(0, false, true, 0); + + if(params.delete_texture_on_shutdown) + m_texture_trash.push_back(rtt); + + return rtt; } -video::IImage* generate_image_from_scratch(std::string name, - IrrlichtDevice *device, SourceImageCache *sourcecache) +video::IImage* TextureSource::generateImageFromScratch(std::string name) { - /*infostream<<"generate_image_from_scratch(): " + /*infostream<<"generateImageFromScratch(): " "\""<getVideoDriver(); + + video::IVideoDriver *driver = m_device->getVideoDriver(); assert(driver); /* @@ -1085,12 +1041,6 @@ video::IImage* generate_image_from_scratch(std::string name, // Find last meta separator in name s32 last_separator_position = name.find_last_of(separator); - //if(last_separator_position == std::npos) - // last_separator_position = -1; - - /*infostream<<"generate_image_from_scratch(): " - <<"last_separator_position="< +/** + * Check and align image to npot2 if required by hardware + * @param image image to check for npot2 alignment + * @param driver driver to use for image operations + * @return image or copy of image aligned to npot2 + */ +video::IImage * Align2Npot2(video::IImage * image, + video::IVideoDriver* driver) +{ + if(image == NULL) { + return image; + } + + core::dimension2d dim = image->getDimension(); + + std::string extensions = (char*) glGetString(GL_EXTENSIONS); + if (extensions.find("GL_OES_texture_npot") != std::string::npos) { + return image; + } + + unsigned int height = npot2(dim.Height); + unsigned int width = npot2(dim.Width); + + if ((dim.Height == height) && + (dim.Width == width)) { + return image; + } + + if (dim.Height > height) { + height *= 2; + } + + if (dim.Width > width) { + width *= 2; + } + + video::IImage *targetimage = + driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d(width, height)); + + if (targetimage != NULL) { + image->copyToScaling(targetimage); + } + image->drop(); + return targetimage; +} + +#endif + +bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg) { - video::IVideoDriver* driver = device->getVideoDriver(); + video::IVideoDriver* driver = m_device->getVideoDriver(); assert(driver); // Stuff starting with [ are special commands if(part_of_name.size() == 0 || part_of_name[0] != '[') { - video::IImage *image = sourcecache->getOrLoad(part_of_name, device); - - if(image == NULL) - { - if(part_of_name != ""){ - errorstream<<"generate_image(): Could not load image \"" + video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); +#ifdef __ANDROID__ + image = Align2Npot2(image,driver); +#endif + if (image == NULL) { + if (part_of_name != "") { + if (part_of_name.find("_normal.png") == std::string::npos){ + errorstream<<"generateImage(): Could not load image \"" < dim = image->getDimension(); baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); image->copyTo(baseimg); - image->drop(); } // Else blit on base. else @@ -1193,132 +1195,60 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, // Position to copy the blitted from in the blitted image core::position2d pos_from(0,0); // Blit - image->copyToWithAlpha(baseimg, pos_to, + /*image->copyToWithAlpha(baseimg, pos_to, core::rect(pos_from, dim), video::SColor(255,255,255,255), - NULL); - // Drop image - image->drop(); + NULL);*/ + blit_with_alpha(image, baseimg, pos_from, pos_to, dim); } + //cleanup + image->drop(); } else { // A special texture modification - /*infostream<<"generate_image(): generating special " + /*infostream<<"generateImage(): generating special " <<"modification \""< dim(1,1); - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - assert(baseimg); - baseimg->setPixel(0,0, video::SColor(255,myrand()%256, - myrand()%256,myrand()%256)); - } - } /* - [crackN + [crack:N:P + [cracko:N:P Adds a cracking texture + N = animation frame count, P = crack progression */ - else if(part_of_name.substr(0,6) == "[crack") + if(part_of_name.substr(0,6) == "[crack") { if(baseimg == NULL) { - errorstream<<"generate_image(): baseimg==NULL " + errorstream<<"generateImage(): baseimg==NULL " <<"for part_of_name=\""< dim_base = baseimg->getDimension(); - /* Load crack image. It is an image with a number of cracking stages horizontally tiled. */ - video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device); - + video::IImage *img_crack = m_sourcecache.getOrLoad( + "crack_anylength.png", m_device); + if(img_crack && progression >= 0) { - // Dimension of original image - core::dimension2d dim_crack - = img_crack->getDimension(); - // Count of crack stages - s32 crack_count = dim_crack.Height / dim_crack.Width; - // Limit progression - if(progression > crack_count-1) - progression = crack_count-1; - // Dimension of a single crack stage - core::dimension2d dim_crack_cropped( - dim_crack.Width, - dim_crack.Width - ); - // Create cropped and scaled crack images - video::IImage *img_crack_cropped = driver->createImage( - video::ECF_A8R8G8B8, dim_crack_cropped); - video::IImage *img_crack_scaled = driver->createImage( - video::ECF_A8R8G8B8, dim_base); - - if(img_crack_cropped && img_crack_scaled) - { - // Crop crack image - v2s32 pos_crack(0, progression*dim_crack.Width); - img_crack->copyTo(img_crack_cropped, - v2s32(0,0), - core::rect(pos_crack, dim_crack_cropped)); - // Scale crack image by copying - img_crack_cropped->copyToScaling(img_crack_scaled); - // Copy or overlay crack image - if(use_overlay) - { - overlay(baseimg, img_crack_scaled); - } - else - { - img_crack_scaled->copyToWithAlpha( - baseimg, - v2s32(0,0), - core::rect(v2s32(0,0), dim_base), - video::SColor(255,255,255,255)); - } - } - - if(img_crack_scaled) - img_crack_scaled->drop(); - - if(img_crack_cropped) - img_crack_cropped->drop(); - + draw_crack(img_crack, baseimg, + use_overlay, frame_count, + progression, driver); img_crack->drop(); } } @@ -1334,7 +1264,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, u32 h0 = stoi(sf.next(":")); infostream<<"combined w="<getOrLoad(filename, device); + video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); if(img) { core::dimension2d dim = img->getDimension(); @@ -1354,10 +1288,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, driver->createImage(video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); - img2->copyToWithAlpha(baseimg, pos_base, + /*img2->copyToWithAlpha(baseimg, pos_base, core::rect(v2s32(0,0), dim), video::SColor(255,255,255,255), - NULL); + NULL);*/ + blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); img2->drop(); } else @@ -1373,7 +1308,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, { if(baseimg == NULL) { - errorstream<<"generate_image(): baseimg==NULL " + errorstream<<"generateImage(): baseimg==NULL " <<"for part_of_name=\""< dim = baseimg->getDimension(); - + // Set alpha to full for(u32 y=0; y dim = baseimg->getDimension(); - + /*video::IImage *oldbaseimg = baseimg; baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); oldbaseimg->copyTo(baseimg); @@ -1474,7 +1409,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, { if(baseimg == NULL) { - errorstream<<"generate_image(): baseimg==NULL " + errorstream<<"generateImage(): baseimg==NULL " <<"for part_of_name=\""<getDimension().Height == npot2(img_top->getDimension().Height)); + assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width)); + assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height)); + assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width)); + + assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height)); + assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width)); +#endif // Create textures from images video::ITexture *texture_top = driver->addTexture( (imagename_top + "__temp__").c_str(), img_top); @@ -1537,7 +1481,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, img_top->drop(); img_left->drop(); img_right->drop(); - + /* Draw a cube mesh into a render target texture */ @@ -1550,32 +1494,26 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left); cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left); - core::dimension2d dim(64,64); - std::string rtt_texture_name = part_of_name + "_RTT"; - - v3f camera_position(0, 1.0, -1.5); - camera_position.rotateXZBy(45); - v3f camera_lookat(0, 0, 0); - core::CMatrix4 camera_projection_matrix; + TextureFromMeshParams params; + params.mesh = cube; + params.dim.set(64, 64); + params.rtt_texture_name = part_of_name + "_RTT"; + // We will delete the rtt texture ourselves + params.delete_texture_on_shutdown = false; + params.camera_position.set(0, 1.0, -1.5); + params.camera_position.rotateXZBy(45); + params.camera_lookat.set(0, 0, 0); // Set orthogonal projection - camera_projection_matrix.buildProjectionMatrixOrthoLH( + params.camera_projection_matrix.buildProjectionMatrixOrthoLH( 1.65, 1.65, 0, 100); - video::SColorf ambient_light(0.2,0.2,0.2); - v3f light_position(10, 100, -50); - video::SColorf light_color(0.5,0.5,0.5); - f32 light_radius = 1000; - - video::ITexture *rtt = generateTextureFromMesh( - cube, device, dim, rtt_texture_name, - camera_position, - camera_lookat, - camera_projection_matrix, - ambient_light, - light_position, - light_color, - light_radius); - + params.ambient_light.set(1.0, 0.2, 0.2, 0.2); + params.light_position.set(10, 100, -50); + params.light_color.set(1.0, 0.5, 0.5, 0.5); + params.light_radius = 1000; + + video::ITexture *rtt = generateTextureFromMesh(params); + // Drop mesh cube->drop(); @@ -1583,19 +1521,21 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, driver->removeTexture(texture_top); driver->removeTexture(texture_left); driver->removeTexture(texture_right); - + if(rtt == NULL) { - baseimg = generate_image_from_scratch( - imagename_top, device, sourcecache); + baseimg = generateImageFromScratch(imagename_top); return true; } // Create image of render target - video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim); + video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim); assert(image); - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + // Cleanup texture + driver->removeTexture(rtt); + + baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim); if(image) { @@ -1603,9 +1543,89 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, image->drop(); } } + /* + [lowpart:percent:filename + Adds the lower part of a texture + */ + else if(part_of_name.substr(0,9) == "[lowpart:") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 percent = stoi(sf.next(":")); + std::string filename = sf.next(":"); + //infostream<<"power part "<createImage(video::ECF_A8R8G8B8, v2u32(16,16)); + video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + if(img) + { + core::dimension2d dim = img->getDimension(); + core::position2d pos_base(0, 0); + video::IImage *img2 = + driver->createImage(video::ECF_A8R8G8B8, dim); + img->copyTo(img2); + img->drop(); + core::position2d clippos(0, 0); + clippos.Y = dim.Height * (100-percent) / 100; + core::dimension2d clipdim = dim; + clipdim.Height = clipdim.Height * percent / 100 + 1; + core::rect cliprect(clippos, clipdim); + img2->copyToWithAlpha(baseimg, pos_base, + core::rect(v2s32(0,0), dim), + video::SColor(255,255,255,255), + &cliprect); + img2->drop(); + } + } + /* + [verticalframe:N:I + Crops a frame of a vertical animation. + N = frame count, I = frame index + */ + else if(part_of_name.substr(0,15) == "[verticalframe:") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 frame_count = stoi(sf.next(":")); + u32 frame_index = stoi(sf.next(":")); + + if(baseimg == NULL){ + errorstream<<"generateImage(): baseimg!=NULL " + <<"for part_of_name=\""<getDimension(); + frame_size.Y /= frame_count; + + video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, + frame_size); + if(!img){ + errorstream<<"generateImage(): Could not create image " + <<"for part_of_name=\""<fill(video::SColor(0,0,0,0)); + + core::dimension2d dim = frame_size; + core::position2d pos_dst(0, 0); + core::position2d pos_src(0, frame_index * frame_size.Y); + baseimg->copyToWithAlpha(img, pos_dst, + core::rect(pos_src, dim), + video::SColor(255,255,255,255), + NULL); + // Replace baseimg + baseimg->drop(); + baseimg = img; + } else { - errorstream<<"generate_image(): Invalid " + errorstream<<"generateImage(): Invalid " " modification: \""< dim = image->getDimension(); - core::dimension2d dim_overlay = overlay->getDimension(); - assert(dim == dim_overlay); + for(u32 y0=0; y0getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } +} - for(u32 y=0; ygetPixel(x,y); - video::SColor c2 = overlay->getPixel(x,y); - u32 a1 = c1.getAlpha(); - u32 a2 = c2.getAlpha(); - if(a1 == 255 && a2 != 0) + s32 src_x = src_pos.X + x0; + s32 src_y = src_pos.Y + y0; + s32 dst_x = dst_pos.X + x0; + s32 dst_y = dst_pos.Y + y0; + video::SColor src_c = src->getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) { - c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255); - c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255); - c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255); + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); } - image->setPixel(x,y,c1); } } +static void draw_crack(video::IImage *crack, video::IImage *dst, + bool use_overlay, s32 frame_count, s32 progression, + video::IVideoDriver *driver) +{ + // Dimension of destination image + core::dimension2d dim_dst = dst->getDimension(); + // Dimension of original image + core::dimension2d dim_crack = crack->getDimension(); + // Count of crack stages + s32 crack_count = dim_crack.Height / dim_crack.Width; + // Limit frame_count + if(frame_count > (s32) dim_dst.Height) + frame_count = dim_dst.Height; + if(frame_count < 1) + frame_count = 1; + // Limit progression + if(progression > crack_count-1) + progression = crack_count-1; + // Dimension of a single crack stage + core::dimension2d dim_crack_cropped( + dim_crack.Width, + dim_crack.Width + ); + // Dimension of the scaled crack stage, + // which is the same as the dimension of a single destination frame + core::dimension2d dim_crack_scaled( + dim_dst.Width, + dim_dst.Height / frame_count + ); + // Create cropped and scaled crack images + video::IImage *crack_cropped = driver->createImage( + video::ECF_A8R8G8B8, dim_crack_cropped); + video::IImage *crack_scaled = driver->createImage( + video::ECF_A8R8G8B8, dim_crack_scaled); + + if(crack_cropped && crack_scaled) + { + // Crop crack image + v2s32 pos_crack(0, progression*dim_crack.Width); + crack->copyTo(crack_cropped, + v2s32(0,0), + core::rect(pos_crack, dim_crack_cropped)); + // Scale crack image by copying + crack_cropped->copyToScaling(crack_scaled); + // Copy or overlay crack image onto each frame + for(s32 i = 0; i < frame_count; ++i) + { + v2s32 dst_pos(0, dim_crack_scaled.Height * i); + if(use_overlay) + { + blit_with_alpha_overlay(crack_scaled, dst, + v2s32(0,0), dst_pos, + dim_crack_scaled); + } + else + { + blit_with_alpha(crack_scaled, dst, + v2s32(0,0), dst_pos, + dim_crack_scaled); + } + } + } + + if(crack_scaled) + crack_scaled->drop(); + + if(crack_cropped) + crack_cropped->drop(); +} + void brighten(video::IImage *image) { if(image == NULL) return; - + core::dimension2d dim = image->getDimension(); for(u32 y=0; y srcdim = src->getDimension(); core::dimension2d dstdim = dst->getDimension();