/* Minetest-c55 Copyright (C) 2010 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 (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. You should have received a copy of the GNU 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 "debug.h" #include "main.h" // for g_settings inline std::string getTexturePath(std::string filename) { std::string texture_path = g_settings.get("texture_path"); if(texture_path == "") return porting::getDataPath(filename.c_str()); else return texture_path + '/' + filename; } 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; // Build main texture atlas if(g_settings.getBool("enable_texture_atlas")) buildMainAtlas(); else dstream<<"INFO: Not building texture atlas."< 0) { GetRequest request = m_get_texture_queue.pop(); dstream<<"INFO: 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); } } u32 TextureSource::getTextureId(const std::string &name) { //dstream<<"INFO: getTextureId(): name="<::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 { dstream<<"INFO: getTextureId(): Queued: name="< result_queue; // Throw a request in m_get_texture_queue.add(name, 0, 0, &result_queue); dstream<<"INFO: 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) { dstream<<"WARNING: Waiting for texture timed out."<::Node *n; n = m_name_to_id.find(name); if(n != NULL) { dstream<<"INFO: getTextureIdDirect(): name="<getValue(); } } dstream<<"INFO: getTextureIdDirect(): name="<=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); dstream<<"INFO: getTextureIdDirect(): Calling itself recursively" " to get base image, name="< 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 ); dstream<<"INFO: 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); dstream<<"INFO: getTextureIdDirect(): name="<= m_atlaspointer_cache.size()=" <= m_atlaspointer_cache.size()) return AtlasPointer(0, NULL); return m_atlaspointer_cache[id].a; } void TextureSource::buildMainAtlas() { dstream<<"TextureSource::buildMainAtlas()"<getVideoDriver(); assert(driver); JMutexAutoLock lock(m_atlaspointer_cache_mutex); // Create an image of the right size core::dimension2d atlas_dim(1024,1024); video::IImage *atlas_img = driver->createImage(video::ECF_A8R8G8B8, atlas_dim); assert(atlas_img); /* A list of stuff to add. This should contain as much of the stuff shown in game as possible, to minimize texture changes. */ 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("mud.png^grass_side.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"); // Padding to disallow texture bleeding s32 padding = 8; /* First pass: generate almost everything */ core::position2d pos_in_atlas(0,0); pos_in_atlas.Y += padding; for(u32 i=0; icreateImageFromFile( 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(); // Tile it a few times in the X direction u16 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); } // 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); } img2->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; } /* 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; iwriteImageToFile(atlas_img, getTexturePath("main_atlas.png").c_str());*/ } video::IImage* generate_image_from_scratch(std::string name, IrrlichtDevice *device) { dstream<<"INFO: generate_image_from_scratch(): " "name="<getVideoDriver(); assert(driver); /* Get the base image */ video::IImage *baseimg = NULL; 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(); assert(driver); // Stuff starting with [ are special commands if(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()); if(image == NULL) { dstream<<"WARNING: 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) { 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); // 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); // Drop image image->drop(); } } else { // A special texture modification dstream<<"INFO: getTextureIdDirect(): generating special " <<"modification \""< dim_base = baseimg->getDimension(); // Crack will be drawn at this size u32 cracksize = 16; // Size of the crack image core::dimension2d dim_crack(cracksize,cracksize); // Position to copy the crack from in the crack image core::position2d pos_other(0, 16 * progression); video::IImage *crackimage = driver->createImageFromFile( getTexturePath("crack.png").c_str()); if(crackimage) { /*crackimage->copyToWithAlpha(baseimg, v2s32(0,0), core::rect(pos_other, dim_base), video::SColor(255,255,255,255), NULL);*/ for(u32 y0=0; y0 pos_base(x0*cracksize, y0*cracksize); crackimage->copyToWithAlpha(baseimg, pos_base, core::rect(pos_other, dim_crack), video::SColor(255,255,255,255), NULL); } crackimage->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(":")); dstream<<"INFO: combined w="<createImageFromFile( getTexturePath(filename.c_str()).c_str()); 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, core::rect(v2s32(0,0), dim), video::SColor(255,255,255,255), NULL); img2->drop(); } else { dstream<<"WARNING: img==NULL"<createImageFromFile(path.c_str()); if(image == NULL) { dstream<<"WARNING: getTextureIdDirect(): Loading path \"" < 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(); } } /* [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) { 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"<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?) */ 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)); // 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); // 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, true, true, 0); //TODO: Free textures of images driver->removeTexture(texture_top); // 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(); } #endif } else { dstream<<"WARNING: getTextureIdDirect(): Invalid " " modification: \""< size = image->getDimension(); u32 barheight = 1; u32 barpad_x = 1; u32 barpad_y = 1; 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); video::SColor active(255,255,0,0); video::SColor inactive(255,0,0,0); for(u32 x0=0; x0setPixel(x,y, *c); } } }