X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Ftile.cpp;h=3c4989ea85a58bf43853de59056f0132644848ff;hb=1e4e64f83195ed1be0aa0c4a237a50de6dd132a9;hp=23fa1129da78bca05411aadacdacca0f2f99816e;hpb=9d09103e481c4979ebb0130a9dee6265d0d6223b;p=dragonfireclient.git diff --git a/src/tile.cpp b/src/tile.cpp index 23fa1129d..3c4989ea8 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,27 +1,36 @@ /* -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 "gamedef.h" +#include "util/string.h" +#include "util/container.h" +#include "util/thread.h" +#include "util/numeric.h" /* A cache from texture name to texture path @@ -67,7 +76,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[] = { @@ -75,7 +84,10 @@ static std::string getImagePath(std::string path) "pcx", "ppm", "psd", "wal", "rgb", NULL }; - + // If there is no extension, add one + if(removeStringEnd(path, extensions) == "") + path = path + ".png"; + // Check paths until something is found to exist const char **ext = extensions; do{ bool r = replace_ext(path, *ext); @@ -112,20 +124,22 @@ std::string getTexturePath(const std::string &filename) /* Check from texture_path */ - std::string texture_path = g_settings.get("texture_path"); + std::string texture_path = g_settings->get("texture_path"); if(texture_path != "") { - std::string testpath = texture_path + '/' + filename; + std::string testpath = texture_path + DIR_DELIM + filename; // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); } - + /* Check from default data directory */ if(fullpath == "") { - std::string testpath = porting::getDataPath(filename.c_str()); + std::string base_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack"; + std::string testpath = base_path + DIR_DELIM + filename; // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); } @@ -137,75 +151,352 @@ std::string getTexturePath(const std::string &filename) return fullpath; } +void clearTextureNameCache() +{ + g_texturename_to_path_cache.clear(); +} + +/* + Stores internal information about a texture. +*/ + +struct TextureInfo +{ + std::string name; + video::ITexture *texture; + video::IImage *img; // The source image + + TextureInfo( + const std::string &name_, + video::ITexture *texture_=NULL, + video::IImage *img_=NULL + ): + name(name_), + texture(texture_), + img(img_) + { + } +}; + +/* + SourceImageCache: A cache used for storing source images. +*/ + +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 + std::map::iterator n; + n = m_images.find(name); + 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){ + toadd = img2; + need_to_grab = false; + } + } + } + + if (need_to_grab) + toadd->grab(); + m_images[name] = toadd; + } + video::IImage* get(const std::string &name) + { + std::map::iterator n; + n = m_images.find(name); + 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) + { + std::map::iterator n; + n = m_images.find(name); + 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()); + if(path == ""){ + infostream<<"SourceImageCache::getOrLoad(): No path found for \"" + <createImageFromFile(path.c_str()); + + if(img){ + m_images[name] = img; + img->grab(); // Grab for caller + } + return img; + } +private: + std::map m_images; +}; + /* TextureSource */ +class TextureSource : public IWritableTextureSource +{ +public: + TextureSource(IrrlichtDevice *device); + virtual ~TextureSource(); + + /* + Example case: + Now, assume a texture with the id 1 exists, and has the name + "stone.png^mineral1". + Then a random thread calls getTextureId for a texture called + "stone.png^mineral1^crack0". + ...Now, WTF should happen? Well: + - getTextureId strips off stuff recursively from the end until + the remaining part is found, or nothing is left when + something is stripped out + + But it is slow to search for textures by names and modify them + like that? + - ContentFeatures is made to contain ids for the basic plain + textures + - Crack textures can be slow by themselves, but the framework + must be fast. + + Example case #2: + - Assume a texture with the id 1 exists, and has the name + "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 + - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and + 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^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" + "stone.png^crack2" + "stone.png^mineral_coal.png" + "stone.png^mineral_coal.png^crack1" + + - If texture specified by name is found from cache, return the + cached id. + - Otherwise generate the texture, add to cache and return id. + Recursion is used to find out the largest found part of the + texture and continue based on it. + + The id 0 points to a NULL texture. It is returned in case of error. + */ + u32 getTextureIdDirect(const std::string &name); + + // Finds out the name of a cached texture. + std::string getTextureName(u32 id); + + /* + If texture specified by the name pointed by the id doesn't + exist, create it, then return the cached texture. + + Can be called from any thread. If called from some other thread + and not found in cache, the call is queued to the main thread + for processing. + */ + video::ITexture* getTexture(u32 id); + + video::ITexture* getTexture(const std::string &name, u32 *id); + + // Returns a pointer to the irrlicht device + virtual IrrlichtDevice* getDevice() + { + return m_device; + } + + 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(); + + // 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. + std::vector m_textureinfo_cache; + // Maps a texture name to an index in the former. + std::map m_name_to_id; + // The two former containers are behind this mutex + 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) +{ + return new TextureSource(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_textureinfo_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; - - // Build main texture atlas - if(g_settings.getBool("enable_texture_atlas")) - buildMainAtlas(); - else - dstream<<"INFO: Not building texture atlas."<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(); -void TextureSource::processQueue() -{ - /* - Fetch textures - */ - if(m_get_texture_queue.size() > 0) + unsigned int textures_before = driver->getTextureCount(); + + for (std::vector::iterator iter = + m_textureinfo_cache.begin(); + iter != m_textureinfo_cache.end(); iter++) { - GetRequest - request = m_get_texture_queue.pop(); + //cleanup texture + if (iter->texture) + driver->removeTexture(iter->texture); - dstream<<"INFO: TextureSource::processQueue(): " - <<"got texture request with " - <<"name="<img) + iter->img->drop(); + } + m_textureinfo_cache.clear(); - GetResult - result; - result.key = request.key; - result.callers = request.callers; - result.item = getTextureIdDirect(request.key); + for (std::list::iterator iter = + m_texture_trash.begin(); iter != m_texture_trash.end(); + iter++) + { + video::ITexture *t = *iter; - request.dest->push_back(result); + //cleanup trashed texture + driver->removeTexture(t); } + + infostream << "~TextureSource() "<< textures_before << "/" + << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) { - //dstream<<"INFO: getTextureId(): name="<::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; } } @@ -218,7 +509,7 @@ u32 TextureSource::getTextureId(const std::string &name) } else { - dstream<<"INFO: getTextureId(): Queued: name="< result_queue; @@ -226,8 +517,8 @@ u32 TextureSource::getTextureId(const std::string &name) // Throw a request in m_get_texture_queue.add(name, 0, 0, &result_queue); - dstream<<"INFO: Waiting for texture from main thread, name=" - < imageTransformDimension(u32 transform, core::dimension2d dim); +// Apply transform to image data +void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); /* This method generates all the textures */ u32 TextureSource::getTextureIdDirect(const std::string &name) { - dstream<<"INFO: getTextureIdDirect(): name="<::Node *n; + std::map::iterator n; n = m_name_to_id.find(name); - if(n != NULL) + if(n != m_name_to_id.end()) { - dstream<<"INFO: getTextureIdDirect(): name="<getValue(); + /*infostream<<"getTextureIdDirect(): \""<second; } } - dstream<<"INFO: getTextureIdDirect(): name="< dim = ap.intsize; + core::dimension2d dim = ti->img->getDimension(); baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - core::position2d pos_to(0,0); - core::position2d pos_from = ap.intpos; - - image->copyTo( + ti->img->copyTo( baseimg, // target v2s32(0,0), // position in target - core::rect(pos_from, dim) // from + core::rect(v2s32(0,0), dim) // from ); - dstream<<"INFO: getTextureIdDirect(): Loaded \"" + /*infostream<<"getTextureIdDirect(): Loaded \"" < 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); - - dstream<<"INFO: getTextureIdDirect(): name="<= m_atlaspointer_cache.size()) + if(id >= m_textureinfo_cache.size()) { - dstream<<"WARNING: TextureSource::getTextureName(): id="<= m_atlaspointer_cache.size()=" - <= m_textureinfo_cache.size()=" + <= m_atlaspointer_cache.size()) - return AtlasPointer(0, NULL); - - return m_atlaspointer_cache[id].a; + if(id >= m_textureinfo_cache.size()) + return NULL; + + return m_textureinfo_cache[id].texture; } -void TextureSource::buildMainAtlas() +video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) { - dstream<<"TextureSource::buildMainAtlas()"<getVideoDriver(); - assert(driver); +void TextureSource::processQueue() +{ + /* + Fetch textures + */ + if(!m_get_texture_queue.empty()) + { + GetRequest + request = m_get_texture_queue.pop(); - JMutexAutoLock lock(m_atlaspointer_cache_mutex); + /*infostream<<"TextureSource::processQueue(): " + <<"got texture request with " + <<"name=\""< atlas_dim(1024,1024); - video::IImage *atlas_img = - driver->createImage(video::ECF_A8R8G8B8, atlas_dim); - assert(atlas_img); + GetResult + result; + result.key = request.key; + result.callers = request.callers; + result.item = getTextureIdDirect(request.key); - /* - A list of stuff to add. This should contain as much of the - stuff shown in game as possible, to minimize texture changes. - */ + request.dest->push_back(result); + } +} - core::array sourcelist; - - sourcelist.push_back("stone.png"); - sourcelist.push_back("mud.png"); - sourcelist.push_back("sand.png"); - sourcelist.push_back("grass.png"); - sourcelist.push_back("grass_footsteps.png"); - sourcelist.push_back("tree.png"); - sourcelist.push_back("tree_top.png"); - sourcelist.push_back("water.png"); - sourcelist.push_back("leaves.png"); - sourcelist.push_back("glass.png"); - sourcelist.push_back("mud.png^grass_side.png"); - sourcelist.push_back("cobble.png"); - sourcelist.push_back("mossycobble.png"); - sourcelist.push_back("gravel.png"); - - sourcelist.push_back("stone.png^mineral_coal.png"); - sourcelist.push_back("stone.png^mineral_iron.png"); - sourcelist.push_back("mud.png^mineral_coal.png"); - sourcelist.push_back("mud.png^mineral_iron.png"); - sourcelist.push_back("sand.png^mineral_coal.png"); - sourcelist.push_back("sand.png^mineral_iron.png"); +void TextureSource::insertSourceImage(const std::string &name, video::IImage *img) +{ + //infostream<<"TextureSource::insertSourceImage(): name="< pos_in_atlas(0,0); + assert(get_current_thread_id() == m_main_thread); - pos_in_atlas.Y += padding; - - for(u32 i=0; igetVideoDriver()); + m_source_image_existence.set(name, true); +} - /*video::IImage *img = driver->createImageFromFile( - getTexturePath(name.c_str()).c_str()); - if(img == NULL) - continue; - - core::dimension2d dim = img->getDimension(); - // Make a copy with the right color format - video::IImage *img2 = - driver->createImage(video::ECF_A8R8G8B8, dim); - img->copyTo(img2); - img->drop();*/ - - // Generate image by name - video::IImage *img2 = generate_image_from_scratch(name, m_device); - if(img2 == NULL) - { - dstream<<"WARNING: TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""< dim = img2->getDimension(); + video::IVideoDriver* driver = m_device->getVideoDriver(); - // Don't add to atlas if image is large - core::dimension2d max_size_in_atlas(32,32); - if(dim.Width > max_size_in_atlas.Width - || dim.Height > max_size_in_atlas.Height) - { - dstream<<"INFO: TextureSource::buildMainAtlas(): Not adding " - <<"\""<name); + // Create texture from resulting image + video::ITexture *t = NULL; + if(img) + t = driver->addTexture(ti->name.c_str(), img); + video::ITexture *t_old = ti->texture; + // Replace texture + ti->texture = t; + ti->img = img; + + if (t_old != 0) + m_texture_trash.push_back(t_old); + } +} - // Stop making atlas if atlas is full - if(pos_in_atlas.Y + dim.Height > atlas_dim.Height) - { - dstream<<"WARNING: TextureSource::buildMainAtlas(): " - <<"Atlas is full, not adding more textures." - <copyToWithAlpha(atlas_img, - pos_in_atlas + v2s32(j*dim.Width,0), - core::rect(v2s32(0,0), dim), - video::SColor(255,255,255,255), - NULL); - } +video::ITexture* TextureSource::generateTextureFromMesh( + const TextureFromMeshParams ¶ms) +{ + video::IVideoDriver *driver = m_device->getVideoDriver(); + assert(driver); - // Copy the borders a few times to disallow texture bleeding - for(u32 side=0; side<2; side++) // top and bottom - for(s32 y0=0; y0queryFeature(video::EVDF_RENDER_TO_TARGET) == false) + { + static bool warned = false; + if(!warned) { - s32 dst_y; - s32 src_y; - if(side==0) - { - dst_y = y0 + pos_in_atlas.Y + dim.Height; - src_y = pos_in_atlas.Y + dim.Height - 1; - } - else - { - dst_y = -y0 + pos_in_atlas.Y-1; - src_y = pos_in_atlas.Y; - } - s32 x = x0 + pos_in_atlas.X * dim.Width; - video::SColor c = atlas_img->getPixel(x, src_y); - atlas_img->setPixel(x,dst_y,c); + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"EVDF_RENDER_TO_TARGET not supported."<drop(); - - /* - Add texture to caches - */ - - // Get next id - u32 id = m_atlaspointer_cache.size(); - - // Create AtlasPointer - AtlasPointer ap(id); - ap.atlas = NULL; // Set on the second pass - ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width, - (float)pos_in_atlas.Y/(float)atlas_dim.Height); - ap.size = v2f((float)dim.Width/(float)atlas_dim.Width, - (float)dim.Width/(float)atlas_dim.Height); - ap.tiled = xwise_tiling; - - // Create SourceAtlasPointer and add to containers - SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim); - m_atlaspointer_cache.push_back(nap); - m_name_to_id.insert(name, id); - - // Increment position - pos_in_atlas.Y += dim.Height + padding * 2; + return NULL; } - /* - Make texture - */ - video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img); - assert(t); - - /* - Second pass: set texture pointer in generated AtlasPointers - */ - for(u32 i=0; iaddRenderTargetTexture( + params.dim, params.rtt_texture_name.c_str(), + video::ECF_A8R8G8B8); + if(rtt == NULL) { - std::string name = sourcelist[i]; - if(m_name_to_id.find(name) == NULL) - continue; - u32 id = m_name_to_id[name]; - //dstream<<"id of name "<writeImageToFile(atlas_img, - getTexturePath("main_atlas.png").c_str());*/ + // Set render target + driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0)); + + // Get a scene manager + scene::ISceneManager *smgr_main = m_device->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(); + + // NOTE: The scene nodes should not be dropped, otherwise + // smgr->drop() segfaults + /*cube->drop(); + camera->drop(); + light->drop();*/ + // 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) +video::IImage* TextureSource::generateImageFromScratch(std::string name) { - dstream<<"INFO: generate_image_from_scratch(): " - "name="<getVideoDriver(); + /*infostream<<"generateImageFromScratch(): " + "\""<getVideoDriver(); assert(driver); /* @@ -685,19 +925,7 @@ video::IImage* generate_image_from_scratch(std::string name, char separator = '^'; // Find last meta separator in name - s32 last_separator_position = -1; - for(s32 i=name.size()-1; i>=0; i--) - { - if(name[i] == separator) - { - last_separator_position = i; - break; - } - } - - /*dstream<<"INFO: generate_image_from_scratch(): " - <<"last_separator_position="<getVideoDriver(); + video::IVideoDriver* driver = m_device->getVideoDriver(); assert(driver); // Stuff starting with [ are special commands - if(part_of_name[0] != '[') + if(part_of_name.size() == 0 || part_of_name[0] != '[') { - // A normal texture; load it from a file - std::string path = getTexturePath(part_of_name.c_str()); - dstream<<"INFO: getTextureIdDirect(): Loading path \""<createImageFromFile(path.c_str()); + video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); if(image == NULL) { - dstream<<"WARNING: Could not load image \""< dim(2,2); @@ -782,7 +999,7 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, // If base image is NULL, load as base. if(baseimg == NULL) { - dstream<<"INFO: Setting "< dim = image->getDimension(); baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); image->copyTo(baseimg); - image->drop(); } // Else blit on base. else { - dstream<<"INFO: Blitting "< dim = image->getDimension(); //core::dimension2d dim(16,16); @@ -805,122 +1021,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 - dstream<<"INFO: getTextureIdDirect(): generating special " + /*infostream<<"generateImage(): generating special " <<"modification \""< dim_base = baseimg->getDimension(); - + // Crack image number and overlay option + bool use_overlay = (part_of_name[6] == 'o'); + Strfnd sf(part_of_name); + sf.next(":"); + s32 frame_count = stoi(sf.next(":")); + s32 progression = stoi(sf.next(":")); + /* Load crack image. It is an image with a number of cracking stages horizontally tiled. */ - video::IImage *img_crack = driver->createImageFromFile( - getTexturePath("crack.png").c_str()); - - if(img_crack) + 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 - u32 crack_count = dim_crack.Height / dim_crack.Width; - // Limit progression - if(progression > crack_count-1) - progression = crack_count-1; - // Dimension of a single scaled crack stage - core::dimension2d dim_crack_scaled_single( - dim_base.Width, - dim_base.Height - ); - // Dimension of scaled size - core::dimension2d dim_crack_scaled( - dim_crack_scaled_single.Width, - dim_crack_scaled_single.Height * crack_count - ); - // Create scaled crack image - video::IImage *img_crack_scaled = driver->createImage( - video::ECF_A8R8G8B8, dim_crack_scaled); - if(img_crack_scaled) - { - // Scale crack image by copying - img_crack->copyToScaling(img_crack_scaled); - - // Position to copy the crack from - core::position2d pos_crack_scaled( - 0, - dim_crack_scaled_single.Height * progression - ); - - // This tiling does nothing currently but is useful - for(u32 y0=0; y0 pos_base( - x0*dim_crack_scaled_single.Width, - y0*dim_crack_scaled_single.Height - ); - // Rectangle to copy the crack from on the scaled image - core::rect rect_crack_scaled( - pos_crack_scaled, - dim_crack_scaled_single - ); - // Copy it - img_crack_scaled->copyToWithAlpha(baseimg, pos_base, - rect_crack_scaled, - video::SColor(255,255,255,255), - NULL); - } - - img_crack_scaled->drop(); - } - + draw_crack(img_crack, baseimg, + use_overlay, frame_count, + progression, driver); img_crack->drop(); } } @@ -934,107 +1088,168 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); - dstream<<"INFO: combined w="<createImageFromFile( - getTexturePath(filename.c_str()).c_str()); + video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); if(img) { core::dimension2d dim = img->getDimension(); - dstream<<"INFO: Size "< pos_base(x, y); video::IImage *img2 = 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 { - dstream<<"WARNING: img==NULL"< dim = baseimg->getDimension(); + + // Set alpha to full + for(u32 y=0; ygetPixel(x,y); + c.setAlpha(255); + baseimg->setPixel(x,y,c); + } + } + /* + "[makealpha:R,G,B" + Convert one color to transparent. + */ + else if(part_of_name.substr(0,11) == "[makealpha:") + { + if(baseimg == NULL) + { + errorstream<<"generateImage(): baseimg==NULL " + <<"for part_of_name=\""<createImageFromFile(path.c_str()); + core::dimension2d dim = baseimg->getDimension(); - if(image == NULL) + /*video::IImage *oldbaseimg = baseimg; + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + oldbaseimg->copyTo(baseimg); + oldbaseimg->drop();*/ + + // Set alpha to full + for(u32 y=0; ygetPixel(x,y); + u32 r = c.getRed(); + u32 g = c.getGreen(); + u32 b = c.getBlue(); + if(!(r == r1 && g == g1 && b == b1)) + continue; + c.setAlpha(0); + baseimg->setPixel(x,y,c); } - else + } + /* + "[transformN" + Rotates and/or flips the image. + + N can be a number (between 0 and 7) or a transform name. + Rotations are counter-clockwise. + 0 I identity + 1 R90 rotate by 90 degrees + 2 R180 rotate by 180 degrees + 3 R270 rotate by 270 degrees + 4 FX flip X + 5 FXR90 flip X then rotate by 90 degrees + 6 FY flip Y + 7 FYR90 flip Y then rotate by 90 degrees + + Note: Transform names can be concatenated to produce + their product (applies the first then the second). + The resulting transform will be equivalent to one of the + eight existing ones, though (see: dihedral group). + */ + else if(part_of_name.substr(0,10) == "[transform") + { + if(baseimg == NULL) { - core::dimension2d dim = image->getDimension(); - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - - // Set alpha to full - for(u32 y=0; ygetPixel(x,y); - c.setAlpha(255); - image->setPixel(x,y,c); - } - // Blit - image->copyTo(baseimg); - - image->drop(); + errorstream<<"generateImage(): baseimg==NULL " + <<"for part_of_name=\""< dim = imageTransformDimension( + transform, baseimg->getDimension()); + video::IImage *image = driver->createImage( + baseimg->getColorFormat(), dim); + assert(image); + imageTransform(transform, baseimg, image); + baseimg->drop(); + baseimg = image; } /* [inventorycube{topimage{leftimage{rightimage @@ -1048,9 +1263,9 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, { if(baseimg != NULL) { - dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL " - <<"for part_of_name="<queryFeature(video::EVDF_RENDER_TO_TARGET) == false) - { - dstream<<"WARNING: getTextureIdDirect(): EVDF_RENDER_TO_TARGET" - " not supported. Creating fallback image"<addTexture( + (imagename_left + "__temp__").c_str(), img_left); + video::ITexture *texture_right = driver->addTexture( + (imagename_right + "__temp__").c_str(), img_right); + assert(texture_top && texture_left && texture_right); + // Drop images img_top->drop(); img_left->drop(); img_right->drop(); - // Create render target texture - video::ITexture *rtt = NULL; - std::string rtt_name = part_of_name + "_RTT"; - rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(), - video::ECF_A8R8G8B8); - assert(rtt); - - // Set render target - driver->setRenderTarget(rtt, true, true, - video::SColor(0,0,0,0)); - - // Get a scene manager - scene::ISceneManager *smgr_main = device->getSceneManager(); - assert(smgr_main); - scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); - assert(smgr); - /* - Create scene: - - An unit cube is centered at 0,0,0 - - Camera looks at cube from Y+, Z- towards Y-, Z+ - NOTE: Cube has to be changed to something else because - the textures cannot be set individually (or can they?) + Draw a cube mesh into a render target texture */ - - scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1, - v3f(0,0,0), v3f(0, 45, 0)); - // Set texture of cube - cube->setMaterialTexture(0, texture_top); - //cube->setMaterialFlag(video::EMF_LIGHTING, false); - cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false); - cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - - scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, - v3f(0, 1.0, -1.5), v3f(0, 0, 0)); + scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1)); + setMeshColor(cube, video::SColor(255, 255, 255, 255)); + cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top); + cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top); + cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right); + cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right); + cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left); + cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left); + + 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 - core::CMatrix4 pm; - pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100); - camera->setProjectionMatrix(pm, true); - - /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0, - v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000); - - smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2)); + params.camera_projection_matrix.buildProjectionMatrixOrthoLH( + 1.65, 1.65, 0, 100); - // Render scene - driver->beginScene(true, true, video::SColor(0,0,0,0)); - smgr->drawAll(); - driver->endScene(); + 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; - // NOTE: The scene nodes should not be dropped, otherwise - // smgr->drop() segfaults - /*cube->drop(); - camera->drop(); - light->drop();*/ - // Drop scene manager - smgr->drop(); + video::ITexture *rtt = generateTextureFromMesh(params); - // Unset render target - driver->setRenderTarget(0, true, true, 0); + // Drop mesh + cube->drop(); - //TODO: Free textures of images + // Free textures of images driver->removeTexture(texture_top); + driver->removeTexture(texture_left); + driver->removeTexture(texture_right); - // Create image of render target - video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim); + if(rtt == NULL) + { + baseimg = generateImageFromScratch(imagename_top); + return true; + } + // Create image of render target + 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) { image->copyTo(baseimg); image->drop(); } -#endif + } + /* + [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 { - dstream<<"WARNING: getTextureIdDirect(): Invalid " + errorstream<<"generateImage(): Invalid " " modification: \""<getPixel(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); + } +} + +/* + Draw an image on top of an another one, using the alpha channel of the + source image; only modify fully opaque pixels in destinaion +*/ +static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size) +{ + for(u32 y0=0; y0getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) + { + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } + } +} + +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 size = image->getDimension(); - - u32 barheight = size.Height/16; - u32 barpad_x = size.Width/16; - u32 barpad_y = size.Height/16; - u32 barwidth = size.Width - barpad_x*2; - v2u32 barpos(barpad_x, size.Height - barheight - barpad_y); + core::dimension2d dim = image->getDimension(); - u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5); + for(u32 y=0; ygetPixel(x,y); + c.setRed(0.5 * 255 + 0.5 * (float)c.getRed()); + c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen()); + c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue()); + image->setPixel(x,y,c); + } +} - video::SColor active(255,255,0,0); - video::SColor inactive(255,0,0,0); - for(u32 x0=0; x0setPixel(x,y, *c); + const std::string &name_i = transform_names[i]; + + if(s[pos] == ('0' + i)) + { + transform = i; + pos++; + break; + } + else if(!(name_i.empty()) && + lowercase(s.substr(pos, name_i.size())) == name_i) + { + transform = i; + pos += name_i.size(); + break; + } } + if(transform < 0) + break; + + // Multiply total_transform and transform in the group D4 + int new_total = 0; + if(transform < 4) + new_total = (transform + total_transform) % 4; + else + new_total = (transform - total_transform + 8) % 4; + if((transform >= 4) ^ (total_transform >= 4)) + new_total += 4; + + total_transform = new_total; } + return total_transform; } +core::dimension2d imageTransformDimension(u32 transform, core::dimension2d dim) +{ + if(transform % 2 == 0) + return dim; + else + return core::dimension2d(dim.Height, dim.Width); +} + +void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) +{ + if(src == NULL || dst == NULL) + return; + + core::dimension2d srcdim = src->getDimension(); + core::dimension2d dstdim = dst->getDimension(); + + assert(dstdim == imageTransformDimension(transform, srcdim)); + assert(transform >= 0 && transform <= 7); + + /* + Compute the transformation from source coordinates (sx,sy) + to destination coordinates (dx,dy). + */ + int sxn = 0; + int syn = 2; + if(transform == 0) // identity + sxn = 0, syn = 2; // sx = dx, sy = dy + else if(transform == 1) // rotate by 90 degrees ccw + sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx + else if(transform == 2) // rotate by 180 degrees + sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy + else if(transform == 3) // rotate by 270 degrees ccw + sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx + else if(transform == 4) // flip x + sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy + else if(transform == 5) // flip x then rotate by 90 degrees ccw + sxn = 2, syn = 0; // sx = dy, sy = dx + else if(transform == 6) // flip y + sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy + else if(transform == 7) // flip y then rotate by 90 degrees ccw + sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx + + for(u32 dy=0; dygetPixel(sx,sy); + dst->setPixel(dx,dy,c); + } +}