From 5884236046c4405eec7cfc12cdde0be9f48058b6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 24 Jul 2014 21:55:09 +0200 Subject: [PATCH] Rework texture generating code, add texture grouping via ( ... ) --- builtin/game/features.lua | 1 + doc/lua_api.txt | 72 +++++++ src/tile.cpp | 399 ++++++++++++++++---------------------- src/tile.h | 2 - 4 files changed, 239 insertions(+), 235 deletions(-) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 2abfe5278..f082b0db8 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -7,6 +7,7 @@ core.features = { get_all_craft_recipes_works = true, use_texture_alpha = true, no_legacy_abms = true, + texture_names_parens = true, } function core.has_feature(arg) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 28509a267..3d8038f87 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -186,6 +186,78 @@ stripping out the file extension: e.g. foomod_foothing.png e.g. foomod_foothing +Texture modifiers +----------------- +There are various texture modifiers that can be used +to generate textures on-the-fly. + +Texture overlaying: + Textures can be overlaid by putting a ^ between them. + Example: default_dirt.png^default_grass_side.png + default_grass_side.png is overlayed over default_dirt.png + +Texture grouping: + Textures can be grouped together by enclosing them in ( and ). + Example: cobble.png^(thing1.png^thing2.png) + A texture for 'thing1.png^thing2.png' is created and the resulting + texture is overlaid over cobble.png. + +Advanced texture modifiers: + [crack::

+ n = animation frame count, p = current animation frame + Draw a step of the crack animation on the texture. + Example: default_cobble.png^[crack:10:1 + + [combine:x:,=:,= + w = width, h = height, x1/x2 = x position, y1/y1 = y position, + file1/file2 = texture to combine + Create a textue of size x and blit to (,) + and blit to (,). + Example: [combine:16x32:0,0=default_cobble.png:0,16=default_wood.png + + [brighten + Brightens the texture. + Example: tnt_tnt_side.png^[brighten + + [noalpha + Makes the texture completly opaque. + Example: default_leaves.png^[noalpha + + [makealpha:,, + Convert one color to transparency. + Example: default_cobble.png^[makealpha:128,128,128 + + [transform + t = transformation(s) to apply + Rotates and/or flips the image. + 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 + Example: default_stone.png^[transformFXR90 + + [inventorycube{{{ + '^' is replaced by '&' in texture names + Create an inventory cube texture using the side textures. + Example: [inventorycube{grass.png{dirt.png&grass_side.png{dirt.png&grass_side.png + Creates an inventorycube with 'grass.png', 'dirt.png^grass_side.png' and + 'dirt.png^grass_side.png' textures + + [lowpart:: + Blit the lower % part of on the texture: + Example: base.png^[lowpart:25:overlay.png + + [verticalframe:: + t = animation frame count, n = current animation frame + Crops the texture to a frame of a vertical animation. + Example: default_torch_animated.png^[verticalframe:16:8 + Sounds ------- Only OGG Vorbis files are supported. diff --git a/src/tile.cpp b/src/tile.cpp index 7cb39eabf..d16d135f5 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -306,27 +306,12 @@ public: /* 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. + - if main thread, generates the texture, adds to cache and returns id. + - if other thread, adds to request queue and waits for main thread. The id 0 points to a NULL texture. It is returned in case of error. */ - u32 getTextureIdDirect(const std::string &name); + u32 getTextureId(const std::string &name); // Finds out the name of a cached texture. std::string getTextureName(u32 id); @@ -382,12 +367,7 @@ public: // 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); + video::IImage* generateImage(const std::string &name); video::ITexture* getNormalTexture(const std::string &name); private: @@ -401,6 +381,13 @@ private: // This should be only accessed from the main thread SourceImageCache m_sourcecache; + // Generate a texture + u32 generateTexture(const 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. + bool generateImagePart(std::string part_of_name, video::IImage *& baseimg); + // Thread-safe cache of what source images are known (true = known) MutexedMap m_source_image_existence; @@ -501,7 +488,7 @@ u32 TextureSource::getTextureId(const std::string &name) */ if(get_current_thread_id() == m_main_thread) { - return getTextureIdDirect(name); + return generateTexture(name); } else { @@ -567,152 +554,51 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); /* This method generates all the textures */ -u32 TextureSource::getTextureIdDirect(const std::string &name) +u32 TextureSource::generateTexture(const std::string &name) { - //infostream<<"getTextureIdDirect(): name=\""<::iterator n; + n = m_name_to_id.find(name); + if (n != m_name_to_id.end()) { + return n->second; + } + } + /* Calling only allowed from main thread */ - if(get_current_thread_id() != m_main_thread) - { - errorstream<<"TextureSource::getTextureIdDirect() " + if (get_current_thread_id() != m_main_thread) { + errorstream<<"TextureSource::generateTexture() " "called not from main thread"<::iterator n; - n = m_name_to_id.find(name); - if(n != m_name_to_id.end()) - { - /*infostream<<"getTextureIdDirect(): \""<second; - } - } - - /*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 = ti->texture->getSize(); - - baseimg = driver->createImage(ti->texture,v2s32(0,0), dim); - - /*infostream<<"getTextureIdDirect(): Loaded \"" - <addTexture(name.c_str(), baseimg); - baseimg->drop(); + tex = driver->addTexture(name.c_str(), img); + img->drop(); } /* @@ -722,7 +608,7 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) JMutexAutoLock lock(m_textureinfo_cache_mutex); u32 id = m_textureinfo_cache.size(); - TextureInfo ti(name, t); + TextureInfo ti(name, tex); m_textureinfo_cache.push_back(ti); m_name_to_id[name] = id; @@ -779,7 +665,7 @@ void TextureSource::processQueue() <<"name=\""<name); + video::IImage *img = generateImage(ti->name); #ifdef __ANDROID__ - img = Align2Npot2(img,driver); + img = Align2Npot2(img, driver); assert(img->getDimension().Height == npot2(img->getDimension().Height)); assert(img->getDimension().Width == npot2(img->getDimension().Width)); #endif // Create texture from resulting image video::ITexture *t = NULL; - if(img) { + if (img) { t = driver->addTexture(ti->name.c_str(), img); img->drop(); } @@ -819,7 +705,7 @@ void TextureSource::rebuildImagesAndTextures() // Replace texture ti->texture = t; - if (t_old != 0) + if (t_old) m_texture_trash.push_back(t_old); } } @@ -1024,51 +910,103 @@ video::ITexture* TextureSource::generateTextureFromMesh( return rtt; } -video::IImage* TextureSource::generateImageFromScratch(std::string name) +video::IImage* TextureSource::generateImage(const std::string &name) { - /*infostream<<"generateImageFromScratch(): " - "\""<getVideoDriver(); - assert(driver); - /* Get the base image */ + const char separator = '^'; + const char paren_open = '('; + const char paren_close = ')'; + + // Find last separator in the name + s32 last_separator_pos = -1; + u8 paren_bal = 0; + for(s32 i = name.size() - 1; i >= 0; i--) { + switch(name[i]) { + case separator: + if (paren_bal == 0) { + last_separator_pos = i; + i = -1; // break out of loop + } + break; + case paren_open: + if (paren_bal == 0) { + errorstream << "generateImage(): unbalanced parentheses" + << "(extranous '(') while generating texture \"" + << name << "\"" << std::endl; + return NULL; + } + paren_bal--; + break; + case paren_close: + paren_bal++; + break; + default: + break; + } + } + if (paren_bal > 0) { + errorstream << "generateImage(): unbalanced parentheses" + << "(missing matching '(') while generating texture \"" + << name << "\"" << std::endl; + return NULL; + } + + video::IImage *baseimg = NULL; - char separator = '^'; - - // Find last meta separator in name - s32 last_separator_position = name.find_last_of(separator); - /* - If separator was found, construct the base name and make the - base image using a recursive call + If separator was found, 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); - baseimg = generateImageFromScratch(base_image_name); + if (last_separator_pos != -1) { + baseimg = generateImage(name.substr(0, last_separator_pos)); } + + video::IVideoDriver* driver = m_device->getVideoDriver(); + assert(driver); + /* Parse out the last part of the name of the image and act according to it */ - std::string last_part_of_name = name.substr(last_separator_position+1); + std::string last_part_of_name = name.substr(last_separator_pos + 1); - // Generate image according to part of name - if(!generateImage(last_part_of_name, baseimg)) - { - errorstream<<"generateImageFromScratch(): " - "failed to generate \""< dim = tmp->getDimension(); + if (!baseimg) + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); + tmp->drop(); + } else if (!generateImagePart(last_part_of_name, baseimg)) { + // Generate image according to part of name + errorstream << "generateImage(): " + "Failed to generate \"" << last_part_of_name << "\"" + << std::endl; + } + + // If no resulting image, print a warning + if (baseimg == NULL) { + errorstream << "generateImage(): baseimg is NULL (attempted to" + " create texture \"" << name << "\")" << std::endl; } return baseimg; @@ -1125,7 +1063,8 @@ video::IImage * Align2Npot2(video::IImage * image, #endif -bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg) +bool TextureSource::generateImagePart(std::string part_of_name, + video::IImage *& baseimg) { video::IVideoDriver* driver = m_device->getVideoDriver(); assert(driver); @@ -1135,7 +1074,7 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas { video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); #ifdef __ANDROID__ - image = Align2Npot2(image,driver); + image = Align2Npot2(image, driver); #endif if (image == NULL) { if (part_of_name != "") { @@ -1221,9 +1160,8 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas */ if(part_of_name.substr(0,6) == "[crack") { - if(baseimg == NULL) - { - errorstream<<"generateImage(): baseimg==NULL " + if (baseimg == NULL) { + errorstream<<"generateImagePart(): baseimg == NULL " <<"for part_of_name=\""< dim = img->getDimension(); infostream<<"Size "<drop(); - } - else - { - infostream<<"img==NULL"<getDimension().Height == npot2(img_top->getDimension().Height)); assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width)); @@ -1469,6 +1403,7 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height)); assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width)); #endif + // Create textures from images video::ITexture *texture_top = driver->addTexture( (imagename_top + "__temp__").c_str(), img_top); @@ -1518,19 +1453,18 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas // Drop mesh cube->drop(); - // Free textures of images + // Free textures driver->removeTexture(texture_top); driver->removeTexture(texture_left); driver->removeTexture(texture_right); - if(rtt == NULL) - { - baseimg = generateImageFromScratch(imagename_top); + if (rtt == NULL) { + baseimg = generateImage(imagename_top); return true; } // Create image of render target - video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim); + video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim); assert(image); // Cleanup texture @@ -1538,8 +1472,7 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim); - if(image) - { + if (image) { image->copyTo(baseimg); image->drop(); } @@ -1592,7 +1525,7 @@ bool TextureSource::generateImage(std::string part_of_name, video::IImage *& bas u32 frame_index = stoi(sf.next(":")); if(baseimg == NULL){ - errorstream<<"generateImage(): baseimg!=NULL " + errorstream<<"generateImagePart(): baseimg != NULL " <<"for part_of_name=\""<createImage(video::ECF_A8R8G8B8, frame_size); if(!img){ - errorstream<<"generateImage(): Could not create image " + errorstream<<"generateImagePart(): Could not create image " <<"for part_of_name=\""<