/*
Minetest-c55
Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
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
#include "filesys.h"
#include "utility.h"
#include "settings.h"
#include "mesh.h"
#include <ICameraSceneNode.h>
#include "log.h"
#include "mapnode.h" // For texture atlas making
#include "nodedef.h" // For texture atlas making
#include "gamedef.h"
#include "utility_string.h"
/*
A cache from texture name to texture path
*/
MutexedMap<std::string, std::string> g_texturename_to_path_cache;
/*
Replaces the filename extension.
eg:
std::string image = "a/image.png"
replace_ext(image, "jpg")
-> image = "a/image.jpg"
Returns true on success.
*/
static 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 "".
*/
static 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
};
// 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);
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.
Checks all supported extensions by replacing the original extension.
If not found, returns "".
Utilizes a thread-safe cache.
*/
std::string getTexturePath(const std::string &filename)
{
std::string fullpath = "";
/*
Check from cache
*/
bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
if(incache)
return fullpath;
/*
Check from texture_path
*/
std::string texture_path = g_settings->get("texture_path");
if(texture_path != "")
{
std::string testpath = texture_path + DIR_DELIM + filename;
// Check all filename extensions. Returns "" if not found.
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 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);
}
// Add to cache (also an empty result is cached)
g_texturename_to_path_cache.set(filename, fullpath);
// Finally return it
return fullpath;
}
/*
An internal variant of AtlasPointer with more data.
(well, more like a wrapper)
*/
struct SourceAtlasPointer
{
std::string name;
AtlasPointer a;
video::IImage *atlas_img; // The source image of the atlas
// Integer variants of position and size
v2s32 intpos;
v2u32 intsize;
SourceAtlasPointer(
const std::string &name_,
AtlasPointer a_=AtlasPointer(0, NULL),
video::IImage *atlas_img_=NULL,
v2s32 intpos_=v2s32(0,0),
v2u32 intsize_=v2u32(0,0)
):
name(name_),
a(a_),
atlas_img(atlas_img_),
intpos(intpos_),
intsize(intsize_)
{
}
};
/*
SourceImageCache: A cache used for storing source images.
*/
class SourceImageCache
{
public:
void insert(const std::string &name, video::IImage *img,
bool prefer_local, video::IVideoDriver *driver)
{
assert(img);
// Remove old image
core::map<std::string, video::IImage*>::Node *n;
n = m_images.find(name);
if(n){
video::IImage *oldimg = n->getValue();
if(oldimg)
oldimg->drop();
}
// Try to use local texture instead if asked to
if(prefer_local){
std::string path = getTexturePath(name.c_str());
if(path != ""){
video::IImage *img2 = driver->createImageFromFile(path.c_str());
if(img2){
m_images[name] = img2;
return;
}
}
}
img->grab();
m_images[name] = img;
}
video::IImage* get(const std::string &name)
{
core::map<std::string, video::IImage*>::Node *n;
n = m_images.find(name);
if(n)
return n->getValue();
return NULL;
}
// Primarily fetches from cache, secondarily tries to read from filesystem
video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
{
core::map<std::string, video::IImage*>::Node *n;
n = m_images.find(name);
if(n){
n->getValue()->grab(); // Grab for caller
return n->getValue();
}
video::IVideoDriver* driver = device->getVideoDriver();
std::string path = getTexturePath(name.c_str());
if(path == ""){
infostream<<"SourceImageCache::getOrLoad(): No path found for \""
<<name<<"\""<<std::endl;
return NULL;
}
infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
<<"\""<<std::endl;
video::IImage *img = driver->createImageFromFile(path.c_str());
// Even if could not be loaded, put as NULL
//m_images[name] = img;
if(img){
m_images[name] = img;
img->grab(); // Grab for caller
}
return img;
}
private:
core::map<std::string, video::IImage*> m_images;
};
/*
TextureSource
*/
class TextureSource : public IWritableTextureSource
{
public:
TextureSource(IrrlichtDevice *device);
~TextureSource();
/*
Example case:
Now, assume a texture with the id 1 exists, and has the name
"stone.png^mineral1".
Then a random thread calls getTextureId for a texture called
"stone.png^mineral1^crack0".
...Now, WTF should happen? Well:
- getTextureId strips off stuff recursively from the end until
the remaining part is found, or nothing is left when
something is stripped out
But it is slow to search for textures by names and modify them
like that?
- ContentFeatures is made to contain ids for the basic plain
textures
- Crack textures can be slow by themselves, but the framework
must be fast.
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 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").
*/
/*
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.
The id 0 points to a NULL texture. It is returned in case of error.
*/
u32 getTextureIdDirect(const std::string &name);
// Finds out the name of a cached texture.
std::string getTextureName(u32 id);
/*
If texture specified by the name pointed by the id doesn't
exist, create it, then return the cached texture.
Can be called from any thread. If called from some other thread
and not found in cache, the call is queued to the main thread
for processing.
*/
AtlasPointer getTexture(u32 id);
AtlasPointer getTexture(const std::string &name)
{
return getTexture(getTextureId(name));
}
// Gets a separate texture
video::ITexture* getTextureRaw(const std::string &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);
// Processes queued texture requests from other threads.
// Shall be called from the main thread.
void processQueue();
// Insert an image into the cache without touching the filesystem.
// Shall be called from the main thread.
|