X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Ftile.cpp;h=1f4456653f5a1867e60defac5eb03a7dd3dc79cd;hb=9a22d02903549c09f059b12f0ec06ea12a19abbb;hp=25d9c00d08ef235225759394a9e1aafdcfd19538;hpb=3de176cc587c4e0601c3c3f5a049e30db6bd2c17;p=dragonfireclient.git diff --git a/src/tile.cpp b/src/tile.cpp index 25d9c00d0..1f4456653 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -18,72 +18,1162 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "tile.h" -#include "main.h" +#include "debug.h" +#include "main.h" // for g_settings +#include "filesys.h" -// A mapping from tiles to paths of textures -const char * g_tile_texture_paths[TILES_COUNT] = +/* + Replaces the filename extension. + eg: + std::string image = "a/image.png" + replace_ext(image, "jpg") + -> image = "a/image.jpg" + Returns true on success. +*/ +inline 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 "". +*/ +inline 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 + }; + + 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. +*/ +inline std::string getTexturePath(std::string filename) { - NULL, - "../data/stone.png", - "../data/water.png", - "../data/grass.png", - "../data/tree.png", - "../data/leaves.png", - "../data/grass_footsteps.png", - "../data/mese.png", - "../data/mud.png", - "../data/tree_top.png", - "../data/mud_with_grass.png", - "../data/cloud.png", - "../data/coalstone.png", -}; - -const char * tile_texture_path_get(u32 i) + std::string texture_path = g_settings.get("texture_path"); + if(texture_path != "") + { + std::string fullpath = texture_path + '/' + filename; + // Check all filename extensions + fullpath = getImagePath(fullpath); + // If found, return it + if(fullpath != "") + return fullpath; + } + std::string fullpath = porting::getDataPath(filename.c_str()); + // Check all filename extensions + fullpath = getImagePath(fullpath); + return fullpath; +} + +TextureSource::TextureSource(IrrlichtDevice *device): + m_device(device), + m_main_atlas_image(NULL), + m_main_atlas_texture(NULL) { - assert(i < TILES_COUNT); + 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; - return g_tile_texture_paths[i]; + // Build main texture atlas + if(g_settings.getBool("enable_texture_atlas")) + buildMainAtlas(); + else + dstream<<"INFO: Not building texture atlas."< 0) { - const char *path = g_tile_texture_paths[i]; + GetRequest + request = m_get_texture_queue.pop(); - video::ITexture *t = NULL; + 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); + } +} - if(path != NULL) +u32 TextureSource::getTextureId(const std::string &name) +{ + //dstream<<"INFO: getTextureId(): name="<::Node *n; + n = m_name_to_id.find(name); + if(n != NULL) { - t = irrlicht->getTexture(path); - assert(t != 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); - //g_tile_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT); - //g_tile_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false); + 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); - g_tile_materials[i].setTexture(0, t); + 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 = 16; + + /* + 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(); + + // Don't add to atlas if image is large + core::dimension2d max_size_in_atlas(32,32); + if(dim.Width > max_size_in_atlas.Width + || dim.Height > max_size_in_atlas.Height) + { + dstream<<"INFO: TextureSource::buildMainAtlas(): Not adding " + <<"\""< atlas_dim.Height) + { + dstream<<"WARNING: TextureSource::buildMainAtlas(): " + <<"Atlas is full, not adding more textures." + <copyToWithAlpha(atlas_img, + pos_in_atlas + v2s32(j*dim.Width,0), + core::rect(v2s32(0,0), dim), + video::SColor(255,255,255,255), + NULL); + } + + // 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(); + + /* + Load crack image. + + It is an image with a number of cracking stages + horizontally tiled. + */ + video::IImage *img_crack = driver->createImageFromFile( + getTexturePath("crack.png").c_str()); + + if(img_crack) + { + // Dimension of original image + core::dimension2d dim_crack + = img_crack->getDimension(); + // Count of crack stages + u32 crack_count = dim_crack.Height / dim_crack.Width; + // Limit progression + if(progression > crack_count-1) + progression = crack_count-1; + // Dimension of a single scaled crack stage + core::dimension2d dim_crack_scaled_single( + dim_base.Width, + dim_base.Height + ); + // Dimension of scaled size + core::dimension2d dim_crack_scaled( + dim_crack_scaled_single.Width, + dim_crack_scaled_single.Height * crack_count + ); + // Create scaled crack image + video::IImage *img_crack_scaled = driver->createImage( + video::ECF_A8R8G8B8, dim_crack_scaled); + if(img_crack_scaled) + { + // Scale crack image by copying + img_crack->copyToScaling(img_crack_scaled); + + // Position to copy the crack from + core::position2d pos_crack_scaled( + 0, + dim_crack_scaled_single.Height * progression + ); + + // This tiling does nothing currently but is useful + for(u32 y0=0; y0 pos_base( + x0*dim_crack_scaled_single.Width, + y0*dim_crack_scaled_single.Height + ); + // Rectangle to copy the crack from on the scaled image + core::rect rect_crack_scaled( + pos_crack_scaled, + dim_crack_scaled_single + ); + // Copy it + img_crack_scaled->copyToWithAlpha(baseimg, pos_base, + rect_crack_scaled, + video::SColor(255,255,255,255), + NULL); + } + + img_crack_scaled->drop(); + } + + 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(":")); + 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); + + 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(); + + // 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 = 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); + + video::SColor active(255,255,0,0); + video::SColor inactive(255,0,0,0); + for(u32 x0=0; x0setPixel(x,y, *c); + } + } }