X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Ftile.cpp;h=aea9665f5eb69ca3568c12447538a81e7a1b8771;hb=2bb559be826f4f82b56fb7916e206df55337c258;hp=29c6b3e671d476238fbd3850ca72052525037e28;hpb=146be1f0b6c058ad0e9fe0aa38e3c1f75a39823b;p=minetest.git diff --git a/src/tile.cpp b/src/tile.cpp index 29c6b3e67..aea9665f5 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,18 +1,18 @@ /* -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. */ @@ -21,14 +21,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #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 "mineral.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" /* A cache from texture name to texture path @@ -82,7 +85,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); @@ -127,13 +133,26 @@ std::string getTexturePath(const std::string &filename) 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 rel_path = std::string("clienttextures")+DIR_DELIM+filename; - std::string testpath = porting::path_data + DIR_DELIM + rel_path; + 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); } @@ -187,10 +206,10 @@ class SourceImageCache { 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(n != m_images.end()){ + video::IImage *oldimg = n->second; if(oldimg) oldimg->drop(); } @@ -210,20 +229,20 @@ class SourceImageCache } 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()); @@ -244,7 +263,7 @@ class SourceImageCache return img; } private: - core::map m_images; + std::map m_images; }; /* @@ -278,12 +297,14 @@ class TextureSource : public IWritableTextureSource 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 MapBlock::getNodeTile() stumbles upon a node which uses - texture id 1, and finds out that NODEMOD_CRACK must be applied - with progression=0 - - It finds out the name of the texture with getTextureName(1), + - 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") + getTextureId("stone.png^mineral1^crack0"). */ @@ -298,8 +319,8 @@ class TextureSource : public IWritableTextureSource Example names: "stone.png" "stone.png^crack2" - "stone.png^blit:mineral_coal.png" - "stone.png^blit:mineral_coal.png^crack1" + "stone.png^mineral_coal.png" + "stone.png^mineral_coal.png^crack1" - If texture specified by name is found from cache, return the cached id. @@ -332,13 +353,37 @@ class TextureSource : public IWritableTextureSource // Gets a separate texture video::ITexture* getTextureRaw(const std::string &name) { - AtlasPointer ap = getTexture(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. @@ -367,11 +412,14 @@ class TextureSource : public IWritableTextureSource // 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_atlaspointer_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; @@ -417,11 +465,11 @@ u32 TextureSource::getTextureId(const std::string &name) See if texture already exists */ JMutexAutoLock lock(m_atlaspointer_cache_mutex); - core::map::Node *n; + 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; } } @@ -468,10 +516,22 @@ u32 TextureSource::getTextureId(const std::string &name) return 0; } -// Draw a progress bar on the image -void make_progressbar(float value, video::IImage *image); +// Overlay image on top of another image (used for cracks) +void overlay(video::IImage *image, video::IImage *overlay); + +// Draw an image on top of an another one, using the alpha channel of the +// source image +static void blit_with_alpha(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size); + // Brighten image void brighten(video::IImage *image); +// Parse a transform name +u32 parseImageTransform(const std::string& s); +// Apply transform to image dimension +core::dimension2d 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". @@ -519,13 +579,13 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) { JMutexAutoLock lock(m_atlaspointer_cache_mutex); - core::map::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; } } @@ -664,7 +724,7 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) 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); + m_name_to_id[name] = id; /*infostream<<"getTextureIdDirect(): " <<"Returning id="< 0) + if(!m_get_texture_queue.empty()) { GetRequest request = m_get_texture_queue.pop(); @@ -736,6 +796,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im assert(get_current_thread_id() == m_main_thread); m_sourcecache.insert(name, img, true, m_device->getVideoDriver()); + m_source_image_existence.set(name, true); } void TextureSource::rebuildImagesAndTextures() @@ -792,7 +853,10 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) JMutexAutoLock lock(m_atlaspointer_cache_mutex); // Create an image of the right size - core::dimension2d atlas_dim(1024,1024); + 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); @@ -808,58 +872,49 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) main content features */ - core::map sourcelist; + std::set sourcelist; for(u16 j=0; jget(j); - for(std::set::const_iterator - i = f.used_texturenames.begin(); - i != f.used_texturenames.end(); i++) + for(u32 i=0; i<6; i++) { - std::string name = *i; - sourcelist[name] = true; - - if(f.often_contains_mineral){ - for(int k=1; k::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) + for(std::set::iterator + i = sourcelist.begin(); + i != sourcelist.end(); ++i) { - std::string name = i.getNode()->getKey(); + std::string name = *i; 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++) + for(std::set::iterator + i = sourcelist.begin(); + i != sourcelist.end(); ++i) { - std::string name = i.getNode()->getKey(); + std::string name = *i; // Generate image by name video::IImage *img2 = generate_image_from_scratch(name, m_device, @@ -873,8 +928,8 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) core::dimension2d dim = img2->getDimension(); - // Don't add to atlas if image is large - core::dimension2d max_size_in_atlas(32,32); + // 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) { @@ -886,14 +941,14 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) // 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 - 256 - padding){ + if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){ errorstream<<"TextureSource::buildMainAtlas(): " <<"Atlas is full, not adding more textures." <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(); /* @@ -948,11 +1026,11 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) bool reuse_old_id = false; u32 id = m_atlaspointer_cache.size(); // Check old id without fetching a texture - core::map::Node *n; + std::map::iterator n; n = m_name_to_id.find(name); // If it exists, we will replace the old definition - if(n){ - id = n->getValue(); + if(n != m_name_to_id.end()){ + id = n->second; reuse_old_id = true; /*infostream<<"TextureSource::buildMainAtlas(): " <<"Replacing old AtlasPointer"<::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) + for(std::set::iterator + i = sourcelist.begin(); + i != sourcelist.end(); ++i) { - std::string name = i.getNode()->getKey(); - if(m_name_to_id.find(name) == NULL) + std::string name = *i; + if(m_name_to_id.find(name) == m_name_to_id.end()) continue; u32 id = m_name_to_id[name]; //infostream<<"id of name "<=0; i--) - { - if(name[i] == separator) - { - last_separator_position = i; - break; - } - } + 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="< 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); + NULL);*/ + blit_with_alpha(image, baseimg, pos_from, pos_to, dim); // Drop image image->drop(); } @@ -1170,6 +1243,15 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, */ if(part_of_name == "[forcesingle") { + // If base image is NULL, create a random color + if(baseimg == NULL) + { + core::dimension2d 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 @@ -1185,8 +1267,19 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, return false; } - // Crack image number - u16 progression = stoi(part_of_name.substr(6)); + // Crack image number and overlay option + s32 progression = 0; + bool use_overlay = false; + if(part_of_name.substr(6,1) == "o") + { + progression = stoi(part_of_name.substr(7)); + use_overlay = true; + } + else + { + progression = stoi(part_of_name.substr(6)); + use_overlay = false; + } // Size of the base image core::dimension2d dim_base = baseimg->getDimension(); @@ -1197,67 +1290,61 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, 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 = sourcecache->getOrLoad( + "crack_anylength.png", device); - if(img_crack) + 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; + s32 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 a single crack stage + core::dimension2d dim_crack_cropped( + dim_crack.Width, + dim_crack.Width ); - // 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 + // 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_crack_scaled); - if(img_crack_scaled) + 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->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; y0copyToScaling(img_crack_scaled); + // Copy or overlay crack image + if(use_overlay) + { + overlay(baseimg, img_crack_scaled); + } + else { - // Position to copy the crack to in the base image - core::position2d 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->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(); } @@ -1274,7 +1361,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, u32 h0 = stoi(sf.next(":")); infostream<<"combined w="<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 @@ -1306,23 +1398,6 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, } } } - /* - [progressbarN - Adds a progress bar, 0.0 <= N <= 1.0 - */ - else if(part_of_name.substr(0,12) == "[progressbar") - { - if(baseimg == NULL) - { - errorstream<<"generate_image(): baseimg==NULL " - <<"for part_of_name=\""<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 &. @@ -1432,23 +1547,6 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, std::string imagename_left = sf.next("{"); std::string imagename_right = sf.next("{"); -#if 1 - // TODO: Create cube with different textures on different sides - - if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false) - { - errorstream<<"generate_image(): 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); + + 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 - 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)); - - // 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(); + 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); - // Unset render target - driver->setRenderTarget(0, true, true, 0); + // Drop mesh + cube->drop(); // Free textures of images - // TODO: When all are used, free them all 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) @@ -1546,7 +1634,86 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, 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 = 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 { @@ -1558,35 +1725,58 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, return true; } -void make_progressbar(float value, video::IImage *image) +void overlay(video::IImage *image, video::IImage *overlay) { - if(image == NULL) + /* + Copy overlay to image, taking alpha into account. + Where image is transparent, don't copy from overlay. + Images sizes must be identical. + */ + if(image == NULL || overlay == 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); - - u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5); + core::dimension2d dim = image->getDimension(); + core::dimension2d dim_overlay = overlay->getDimension(); + assert(dim == dim_overlay); - video::SColor active(255,255,0,0); - video::SColor inactive(255,0,0,0); - for(u32 x0=0; x0getPixel(x,y); + video::SColor c2 = overlay->getPixel(x,y); + u32 a1 = c1.getAlpha(); + u32 a2 = c2.getAlpha(); + if(a1 == 255 && a2 != 0) { - image->setPixel(x,y, *c); + 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); } } @@ -1608,3 +1798,106 @@ void brighten(video::IImage *image) } } +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); + } +}