void TextureSource::buildMainAtlas(class IGameDef *gamedef) { assert(gamedef->tsrc() == this); INodeDefManager *ndef = gamedef->ndef(); infostream<<"TextureSource::buildMainAtlas()"<<std::endl; //return; // Disable (for testing) video::IVideoDriver* driver = m_device->getVideoDriver(); assert(driver); JMutexAutoLock lock(m_atlaspointer_cache_mutex); // Create an image of the right size core::dimension2d<u32> atlas_dim(1024,1024); 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."<<std::endl; return; } /* Grab list of stuff to include in the texture atlas from the main content features */ core::map<std::string, bool> sourcelist; for(u16 j=0; j<MAX_CONTENT+1; j++) { if(j == CONTENT_IGNORE || j == CONTENT_AIR) continue; const ContentFeatures &f = ndef->get(j); for(u32 i=0; i<6; i++) { std::string name = f.tname_tiles[i]; sourcelist[name] = true; } } infostream<<"Creating texture atlas out of textures: "; for(core::map<std::string, bool>::Iterator i = sourcelist.getIterator(); i.atEnd() == false; i++) { std::string name = i.getNode()->getKey(); infostream<<"\""<<name<<"\" "; } infostream<<std::endl; // Padding to disallow texture bleeding s32 padding = 16; s32 column_width = 256; s32 column_padding = 16; /* First pass: generate almost everything */ core::position2d<s32> pos_in_atlas(0,0); pos_in_atlas.Y = padding; for(core::map<std::string, bool>::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 \""<<name<<"\""<<std::endl; continue; } core::dimension2d<u32> dim = img2->getDimension(); // Don't add to atlas if image is large core::dimension2d<u32> max_size_in_atlas(32,32); if(dim.Width > max_size_in_atlas.Width || dim.Height > max_size_in_atlas.Height) { infostream<<"TextureSource::buildMainAtlas(): Not adding " <<"\""<<name<<"\" because image is large"<<std::endl; continue; } // 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){ errorstream<<"TextureSource::buildMainAtlas(): " <<"Atlas is full, not adding more textures." <<std::endl; break; } pos_in_atlas.Y = padding; pos_in_atlas.X += column_width + column_padding; } /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name <<"\" to texture atlas"<<std::endl;*/ // Tile it a few times in the X direction u16 xwise_tiling = column_width / dim.Width; if(xwise_tiling > 16) // Limit to 16 (more gives no benefit) xwise_tiling = 16; for(u32 j=0; j<xwise_tiling; j++) { // Copy the copy to the atlas /*img2->copyToWithAlpha(atlas_img, pos_in_atlas + v2s32(j*dim.Width,0), core::rect<s32>(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<s32>(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; y0<padding; y0++) for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++) { s32 dst_y; s32 src_y; if(side==0) { dst_y = y0 + pos_in_atlas.Y + dim.Height; src_y = pos_in_atlas.Y + dim.Height - 1; } else { dst_y = -y0 + pos_in_atlas.Y-1; src_y = pos_in_atlas.Y; } s32 x = x0 + pos_in_atlas.X; video::SColor c = atlas_img->getPixel(x, src_y); atlas_img->setPixel(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<std::string, u32>::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"<<std::endl;*/ } // 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); if(reuse_old_id) m_atlaspointer_cache[id] = nap; else m_atlaspointer_cache.push_back(nap); m_name_to_id[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(core::map<std::string, bool>::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 "<<name<<" is "<<id<<std::endl; m_atlaspointer_cache[id].a.atlas = t; } /* Write image to file so that it can be inspected */ /*std::string atlaspath = porting::path_user + DIR_DELIM + "generated_texture_atlas.png"; infostream<<"Removing and writing texture atlas for inspection to " <<atlaspath<<std::endl; fs::RecursiveDelete(atlaspath); driver->writeImageToFile(atlas_img, atlaspath.c_str());*/ }
void TextureSource::buildMainAtlas() { infostream<<"TextureSource::buildMainAtlas()"<<std::endl; //return; // Disable (for testing) video::IVideoDriver* driver = m_device->getVideoDriver(); assert(driver); JMutexAutoLock lock(m_atlaspointer_cache_mutex); // Create an image of the right size core::dimension2d<u32> atlas_dim(1024,1024); 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."<<std::endl; return; } /* A list of stuff to include in the texture atlas. It is a single-dimensional texture atlas due to the need to tile textures. It should contain as much of the stuff shown in game as possible, to minimize texture changes. It fills up quickly, so do not add anything that isn't contained in most MapBlocks. E.g. mese isn't suitable but stone is. */ core::array<std::string> 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("glass.png"); sourcelist.push_back("mud.png^grass_side.png"); sourcelist.push_back("cobble.png"); sourcelist.push_back("mossycobble.png"); sourcelist.push_back("gravel.png"); sourcelist.push_back("jungletree.png"); sourcelist.push_back("stone.png^mineral_coal.png"); sourcelist.push_back("stone.png^mineral_iron.png"); // Padding to disallow texture bleeding s32 padding = 16; /* First pass: generate almost everything */ core::position2d<s32> pos_in_atlas(0,0); pos_in_atlas.Y += padding; for(u32 i=0; i<sourcelist.size(); i++) { std::string name = sourcelist[i]; /*video::IImage *img = driver->createImageFromFile( getTexturePath(name.c_str()).c_str()); if(img == NULL) continue; core::dimension2d<u32> 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) { infostream<<"TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl; continue; } core::dimension2d<u32> dim = img2->getDimension(); // Don't add to atlas if image is large core::dimension2d<u32> max_size_in_atlas(32,32); if(dim.Width > max_size_in_atlas.Width || dim.Height > max_size_in_atlas.Height) { infostream<<"TextureSource::buildMainAtlas(): Not adding " <<"\""<<name<<"\" because image is large"<<std::endl; continue; } // Stop making atlas if atlas is full if(pos_in_atlas.Y + dim.Height > atlas_dim.Height) { infostream<<"TextureSource::buildMainAtlas(): " <<"Atlas is full, not adding more textures." <<std::endl; break; } infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name <<"\" to texture atlas"<<std::endl; // Tile it a few times in the X direction u16 xwise_tiling = 16; for(u32 j=0; j<xwise_tiling; j++) { // Copy the copy to the atlas img2->copyToWithAlpha(atlas_img, pos_in_atlas + v2s32(j*dim.Width,0), core::rect<s32>(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; y0<padding; y0++) for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++) { s32 dst_y; s32 src_y; if(side==0) { dst_y = y0 + pos_in_atlas.Y + dim.Height; src_y = pos_in_atlas.Y + dim.Height - 1; } else { dst_y = -y0 + pos_in_atlas.Y-1; src_y = pos_in_atlas.Y; } s32 x = x0 + pos_in_atlas.X * dim.Width; video::SColor c = atlas_img->getPixel(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; i<sourcelist.size(); i++) { std::string name = sourcelist[i]; if(m_name_to_id.find(name) == NULL) continue; u32 id = m_name_to_id[name]; //infostream<<"id of name "<<name<<" is "<<id<<std::endl; m_atlaspointer_cache[id].a.atlas = t; } /* Write image to file so that it can be inspected */ /*driver->writeImageToFile(atlas_img, getTexturePath("main_atlas.png").c_str());*/ }