X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Ftile.cpp;h=7cad1b83692399392cacbf6ede423d0c543fc014;hb=d5029958b9017ad89775bc4f68c4de3db603e618;hp=60e9873c0cd73f8a93fb99d4cc4a2d6d4e60a206;hpb=9f882bf74d452521cb7fb1806ab453aaa28da343;p=oweals%2Fminetest.git diff --git a/src/tile.cpp b/src/tile.cpp index 60e9873c0..7cad1b836 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,27 +1,1903 @@ /* Minetest-c55 -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2010-2011 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 "porting.h" -// For IrrlichtWrapper -//#include "main.h" -//#include +#include "debug.h" +#include "main.h" // for g_settings +#include "filesys.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 "util/string.h" +#include "util/container.h" +#include "util/thread.h" +#include "util/numeric.h" -// Nothing here +/* + A cache from texture name to texture path +*/ +MutexedMap g_texturename_to_path_cache; + +/* + Replaces the filename extension. + eg: + std::string image = "a/image.png" + replace_ext(image, "jpg") + -> image = "a/image.jpg" + Returns true on success. +*/ +static bool replace_ext(std::string &path, const char *ext) +{ + if(ext == NULL) + return false; + // Find place of last dot, fail if \ or / found. + s32 last_dot_i = -1; + for(s32 i=path.size()-1; i>=0; i--) + { + if(path[i] == '.') + { + last_dot_i = i; + break; + } + + if(path[i] == '\\' || path[i] == '/') + break; + } + // If not found, return an empty string + if(last_dot_i == -1) + return false; + // Else make the new path + path = path.substr(0, last_dot_i+1) + ext; + return true; +} + +/* + Find out the full path of an image by trying different filename + extensions. + + If failed, return "". +*/ +static std::string getImagePath(std::string path) +{ + // A NULL-ended list of possible image extensions + const char *extensions[] = { + "png", "jpg", "bmp", "tga", + "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); + if(r == false) + return ""; + if(fs::PathExists(path)) + return path; + } + while((++ext) != NULL); + + return ""; +} + +/* + Gets the path to a texture by first checking if the texture exists + in texture_path and if not, using the data path. + + Checks all supported extensions by replacing the original extension. + + If not found, returns "". + + Utilizes a thread-safe cache. +*/ +std::string getTexturePath(const std::string &filename) +{ + std::string fullpath = ""; + /* + Check from cache + */ + bool incache = g_texturename_to_path_cache.get(filename, &fullpath); + if(incache) + return fullpath; + + /* + Check from texture_path + */ + std::string texture_path = g_settings->get("texture_path"); + if(texture_path != "") + { + std::string testpath = texture_path + DIR_DELIM + 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 + */ + if(fullpath == "") + { + 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); + } + + // Add to cache (also an empty result is cached) + g_texturename_to_path_cache.set(filename, fullpath); + + // Finally return it + return fullpath; +} + +/* + An internal variant of AtlasPointer with more data. + (well, more like a wrapper) +*/ + +struct SourceAtlasPointer +{ + 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; + + SourceAtlasPointer( + const std::string &name_, + AtlasPointer a_=AtlasPointer(0, NULL), + video::IImage *atlas_img_=NULL, + v2s32 intpos_=v2s32(0,0), + v2u32 intsize_=v2u32(0,0) + ): + name(name_), + a(a_), + atlas_img(atlas_img_), + intpos(intpos_), + intsize(intsize_) + { + } +}; + +/* + SourceImageCache: A cache used for storing source images. +*/ + +class SourceImageCache +{ +public: + void insert(const std::string &name, video::IImage *img, + bool prefer_local, video::IVideoDriver *driver) + { + assert(img); + // Remove old image + core::map::Node *n; + n = m_images.find(name); + if(n){ + video::IImage *oldimg = n->getValue(); + if(oldimg) + oldimg->drop(); + } + // 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; + } + } + } + img->grab(); + m_images[name] = img; + } + video::IImage* get(const std::string &name) + { + core::map::Node *n; + n = m_images.find(name); + if(n) + return n->getValue(); + 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; + n = m_images.find(name); + if(n){ + n->getValue()->grab(); // Grab for caller + return n->getValue(); + } + 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()); + // Even if could not be loaded, put as NULL + //m_images[name] = img; + if(img){ + m_images[name] = img; + img->grab(); // Grab for caller + } + return img; + } +private: + core::map m_images; +}; + +/* + TextureSource +*/ + +class TextureSource : public IWritableTextureSource +{ +public: + TextureSource(IrrlichtDevice *device); + ~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^mineral1" and is specified as a part of some atlas. + - 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^mineral1^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. + */ + 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; + } + + // Gets a separate texture atlas pointer + AtlasPointer getTextureRawAP(const AtlasPointer &ap) + { + return getTexture(getTextureName(ap.id) + "^[forcesingle"); + } + + // Returns a pointer to the irrlicht device + virtual IrrlichtDevice* getDevice() + { + 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); + +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; + // Maps a texture name to an index in the former. + core::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; + + // Queued texture fetches (to be processed by the main thread) + RequestQueue m_get_texture_queue; +}; + +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) +{ + 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("")); + m_name_to_id[""] = 0; +} + +TextureSource::~TextureSource() +{ +} + +u32 TextureSource::getTextureId(const std::string &name) +{ + //infostream<<"getTextureId(): \""<::Node *n; + n = m_name_to_id.find(name); + if(n != NULL) + { + return n->getValue(); + } + } + + /* + Get texture + */ + if(get_current_thread_id() == m_main_thread) + { + return getTextureIdDirect(name); + } + else + { + infostream<<"getTextureId(): Queued: name=\""< result_queue; + + // Throw a request in + m_get_texture_queue.add(name, 0, 0, &result_queue); + + infostream<<"Waiting for texture from main thread, name=\"" + < + result = result_queue.pop_front(1000); + + // Check that at least something worked OK + assert(result.key == name); + + return result.item; + } + catch(ItemNotFoundException &e) + { + infostream<<"Waiting for texture timed out."< imageTransformDimension(u32 transform, core::dimension2d dim); +// 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 +*/ +u32 TextureSource::getTextureIdDirect(const std::string &name) +{ + //infostream<<"getTextureIdDirect(): name=\""<::Node *n; + n = m_name_to_id.find(name); + if(n != NULL) + { + /*infostream<<"getTextureIdDirect(): \""<getValue(); + } + } + + /*infostream<<"getTextureIdDirect(): \""<=0; i--) + { + if(name[i] == separator) + { + last_separator_position = i; + break; + } + } + /* + If separator was found, construct the base name and make the + base image using a recursive call + */ + std::string base_image_name; + if(last_separator_position != -1) + { + // Construct base name + base_image_name = name.substr(0, last_separator_position); + /*infostream<<"getTextureIdDirect(): Calling itself recursively" + " to get base image of \""< dim = ap.intsize; + + 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 + ); + + /*infostream<<"getTextureIdDirect(): Loaded \"" + <addTexture(name.c_str(), baseimg); + } + + /* + 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()) + { + errorstream<<"TextureSource::getTextureName(): id="<= m_atlaspointer_cache.size()=" + <= m_atlaspointer_cache.size()) + return AtlasPointer(0, NULL); + + return m_atlaspointer_cache[id].a; +} + +void TextureSource::updateAP(AtlasPointer &ap) +{ + AtlasPointer ap2 = getTexture(ap.id); + ap = ap2; +} + +void TextureSource::processQueue() +{ + /* + Fetch textures + */ + if(m_get_texture_queue.size() > 0) + { + GetRequest + request = m_get_texture_queue.pop(); + + /*infostream<<"TextureSource::processQueue(): " + <<"got texture request with " + <<"name=\""< + result; + result.key = request.key; + result.callers = request.callers; + result.item = getTextureIdDirect(request.key); + + request.dest->push_back(result); + } +} + +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();*/ + + 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; + }*/ + + // Recreate textures + for(u32 i=0; iname, m_device, &m_sourcecache); + // Create texture from resulting image + video::ITexture *t = NULL; + if(img) + t = driver->addTexture(sap->name.c_str(), img); + + // 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(); + } +} + +void TextureSource::buildMainAtlas(class IGameDef *gamedef) +{ + assert(gamedef->tsrc() == this); + INodeDefManager *ndef = gamedef->ndef(); + + infostream<<"TextureSource::buildMainAtlas()"<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."< sourcelist; + + for(u16 j=0; jget(j); + for(u32 i=0; i<6; i++) + { + std::string name = f.tiledef[i].name; + 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<<"\""< 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(); + + // 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(); + + // 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 " + <<"\""< 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); + } + + // 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); + } + + 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); + } + + img2->drop(); + + /* + 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); + + /* + Second pass: set texture pointer in generated AtlasPointers + */ + for(core::map::Iterator + i = sourcelist.getIterator(); + i.atEnd() == false; i++) + { + 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());*/ +} + +video::IImage* generate_image_from_scratch(std::string name, + IrrlichtDevice *device, SourceImageCache *sourcecache) +{ + /*infostream<<"generate_image_from_scratch(): " + "\""<getVideoDriver(); + assert(driver); + + /* + Get the base image + */ + + video::IImage *baseimg = NULL; + + char separator = '^'; + + // 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="<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 \"" + < dim(2,2); + core::dimension2d dim(1,1); + image = driver->createImage(video::ECF_A8R8G8B8, dim); + assert(image); + /*image->setPixel(0,0, video::SColor(255,255,0,0)); + image->setPixel(1,0, video::SColor(255,0,255,0)); + image->setPixel(0,1, video::SColor(255,0,0,255)); + image->setPixel(1,1, video::SColor(255,255,0,255));*/ + image->setPixel(0,0, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256)); + /*image->setPixel(1,0, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256)); + image->setPixel(0,1, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256)); + image->setPixel(1,1, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256));*/ + } + + // If base image is NULL, load as base. + if(baseimg == NULL) + { + //infostream<<"Setting "< dim = image->getDimension(); + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + image->copyTo(baseimg); + image->drop(); + } + // Else blit on base. + else + { + //infostream<<"Blitting "< dim = image->getDimension(); + //core::dimension2d dim(16,16); + // Position to copy the blitted to in the base image + core::position2d pos_to(0,0); + // Position to copy the blitted from in the blitted image + core::position2d pos_from(0,0); + // Blit + /*image->copyToWithAlpha(baseimg, pos_to, + core::rect(pos_from, dim), + video::SColor(255,255,255,255), + NULL);*/ + blit_with_alpha(image, baseimg, pos_from, pos_to, dim); + // Drop image + image->drop(); + } + } + else + { + // A special texture modification + + /*infostream<<"generate_image(): 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 + Adds a cracking texture + */ + else if(part_of_name.substr(0,6) == "[crack") + { + if(baseimg == NULL) + { + errorstream<<"generate_image(): 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_anylength.png", 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));*/ + blit_with_alpha(img_crack_scaled, baseimg, + v2s32(0,0), v2s32(0,0), dim_base); + } + } + + if(img_crack_scaled) + img_crack_scaled->drop(); + + if(img_crack_cropped) + img_crack_cropped->drop(); + + img_crack->drop(); + } + } + /* + [combine:WxH:X,Y=filename:X,Y=filename2 + Creates a bigger texture from an amount of smaller ones + */ + else if(part_of_name.substr(0,8) == "[combine") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 w0 = stoi(sf.next("x")); + u32 h0 = stoi(sf.next(":")); + infostream<<"combined w="<getOrLoad(filename, device); + if(img) + { + core::dimension2d dim = img->getDimension(); + infostream<<"Size "< pos_base(x, y); + video::IImage *img2 = + driver->createImage(video::ECF_A8R8G8B8, dim); + img->copyTo(img2); + img->drop(); + /*img2->copyToWithAlpha(baseimg, pos_base, + core::rect(v2s32(0,0), dim), + video::SColor(255,255,255,255), + NULL);*/ + blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); + img2->drop(); + } + else + { + infostream<<"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<<"generate_image(): baseimg==NULL " + <<"for part_of_name=\""< dim = baseimg->getDimension(); + + /*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); + } + } + /* + "[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) + { + errorstream<<"generate_image(): 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 + In every subimage, replace ^ with &. + Create an "inventory cube". + NOTE: This should be used only on its own. + Example (a grass block (not actually used in game): + "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png" + */ + else if(part_of_name.substr(0,14) == "[inventorycube") + { + if(baseimg != NULL) + { + errorstream<<"generate_image(): baseimg!=NULL " + <<"for part_of_name=\""<addTexture( + (imagename_top + "__temp__").c_str(), img_top); + video::ITexture *texture_left = driver->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(); + + /* + Draw a cube mesh into a render target texture + */ + 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); + + 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; + // Set orthogonal projection + 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); + + // Drop mesh + cube->drop(); + + // Free textures of images + driver->removeTexture(texture_top); + driver->removeTexture(texture_left); + driver->removeTexture(texture_right); + + if(rtt == NULL) + { + baseimg = generate_image_from_scratch( + imagename_top, device, sourcecache); + return true; + } + + // Create image of render target + video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim); + assert(image); + + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + + if(image) + { + image->copyTo(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 = sourcecache->getOrLoad(filename, 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<<"generate_image(): 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<<"generate_image(): 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 " + " modification: \""< dim = image->getDimension(); + core::dimension2d dim_overlay = overlay->getDimension(); + assert(dim == dim_overlay); + + 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) + { + 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); + } + image->setPixel(x,y,c1); + } +} + +/* + Draw an image on top of an another one, using the alpha channel of the + source image + + This exists because IImage::copyToWithAlpha() doesn't seem to always + work. +*/ +static void blit_with_alpha(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); + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } +} + +void brighten(video::IImage *image) +{ + if(image == NULL) + return; + + core::dimension2d dim = image->getDimension(); + + 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); + } +} + +u32 parseImageTransform(const std::string& s) +{ + int total_transform = 0; + + std::string transform_names[8]; + transform_names[0] = "i"; + transform_names[1] = "r90"; + transform_names[2] = "r180"; + transform_names[3] = "r270"; + transform_names[4] = "fx"; + transform_names[6] = "fy"; + + std::size_t pos = 0; + while(pos < s.size()) + { + int transform = -1; + for(int i = 0; i <= 7; ++i) + { + 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); + } +}