2 Commits

Author SHA1 Message Date
de259280b6 Fix travis 2016-12-05 12:43:41 +01:00
75009ad49c Initial work on using FreeImage 2016-12-05 12:40:27 +01:00
39 changed files with 1065 additions and 2370 deletions

View File

@ -2,16 +2,23 @@ language: cpp
compiler:
- gcc
- clang
dist: bionic
sudo: false
addons:
apt:
sources:
- llvm-toolchain-precise-3.8
- ubuntu-toolchain-r-test
packages:
- cmake
- libgd-dev
- libfreeimage-dev
- libsqlite3-dev
- libleveldb-dev
- libpq-dev
- postgresql-server-dev-all
- p7zip
- g++-6
- clang-3.8
before_install:
- # Nothing ever works correctly with precise, use a custom libleveldb build
- wget http://minetest.kitsunemimi.pw/libleveldb-1.18-ubuntu12.04.7z
- 7zr x -olibleveldb libleveldb-1.18-ubuntu12.04.7z
script: ./util/travis/script.sh
notifications:
email: false

View File

@ -1,160 +0,0 @@
#include <stdint.h>
#include <string>
#include <iostream>
#include <sstream>
#include "BlockDecoder.h"
#include "ZlibDecompressor.h"
static inline uint16_t readU16(const unsigned char *data)
{
return data[0] << 8 | data[1];
}
static int readBlockContent(const unsigned char *mapData, u8 contentWidth, unsigned int datapos)
{
if (contentWidth == 2) {
size_t index = datapos << 1;
return (mapData[index] << 8) | mapData[index + 1];
} else {
u8 param = mapData[datapos];
if (param <= 0x7f)
return param;
else
return (int(param) << 4) | (int(mapData[datapos + 0x2000]) >> 4);
}
}
BlockDecoder::BlockDecoder()
{
reset();
}
void BlockDecoder::reset()
{
m_blockAirId = -1;
m_blockIgnoreId = -1;
m_nameMap.clear();
m_version = 0;
m_contentWidth = 0;
m_mapData = ustring();
}
void BlockDecoder::decode(const ustring &datastr)
{
const unsigned char *data = datastr.c_str();
size_t length = datastr.length();
// TODO: bounds checks
uint8_t version = data[0];
//uint8_t flags = data[1];
if (version < 22) {
std::ostringstream oss;
oss << "Unsupported map version " << (int)version;
throw std::runtime_error(oss.str());
}
m_version = version;
size_t dataOffset = 0;
if (version >= 27)
dataOffset = 4;
else
dataOffset = 2;
uint8_t contentWidth = data[dataOffset];
dataOffset++;
uint8_t paramsWidth = data[dataOffset];
dataOffset++;
if (contentWidth != 1 && contentWidth != 2)
throw std::runtime_error("unsupported map version (contentWidth)");
if (paramsWidth != 2)
throw std::runtime_error("unsupported map version (paramsWidth)");
m_contentWidth = contentWidth;
ZlibDecompressor decompressor(data, length);
decompressor.setSeekPos(dataOffset);
m_mapData = decompressor.decompress();
decompressor.decompress(); // unused metadata
dataOffset = decompressor.seekPos();
// Skip unused data
if (version == 23)
dataOffset += 1;
if (version == 24) {
uint8_t ver = data[dataOffset++];
if (ver == 1) {
uint16_t num = readU16(data + dataOffset);
dataOffset += 2;
dataOffset += 10 * num;
}
}
// Skip unused static objects
dataOffset++; // Skip static object version
int staticObjectCount = readU16(data + dataOffset);
dataOffset += 2;
for (int i = 0; i < staticObjectCount; ++i) {
dataOffset += 13;
uint16_t dataSize = readU16(data + dataOffset);
dataOffset += dataSize + 2;
}
dataOffset += 4; // Skip timestamp
// Read mapping
{
dataOffset++; // mapping version
uint16_t numMappings = readU16(data + dataOffset);
dataOffset += 2;
for (int i = 0; i < numMappings; ++i) {
uint16_t nodeId = readU16(data + dataOffset);
dataOffset += 2;
uint16_t nameLen = readU16(data + dataOffset);
dataOffset += 2;
std::string name(reinterpret_cast<const char *>(data) + dataOffset, nameLen);
if (name == "air")
m_blockAirId = nodeId;
else if (name == "ignore")
m_blockIgnoreId = nodeId;
else
m_nameMap[nodeId] = name;
dataOffset += nameLen;
}
}
// Node timers
if (version >= 25) {
dataOffset++;
uint16_t numTimers = readU16(data + dataOffset);
dataOffset += 2;
dataOffset += numTimers * 10;
}
}
bool BlockDecoder::isEmpty() const
{
// only contains ignore and air nodes?
return m_nameMap.empty();
}
std::string BlockDecoder::getNode(u8 x, u8 y, u8 z) const
{
unsigned int position = x + (y << 4) + (z << 8);
int content = readBlockContent(m_mapData.c_str(), m_contentWidth, position);
if (content == m_blockAirId || content == m_blockIgnoreId)
return "";
NameMap::const_iterator it = m_nameMap.find(content);
if (it == m_nameMap.end()) {
std::cerr << "Skipping node with invalid ID." << std::endl;
return "";
}
return it->second;
}
u8 BlockDecoder::getParam1(u8 x, u8 y, u8 z) const
{
unsigned int position = x + (y << 4) + (z << 8);
unsigned int offset = (m_contentWidth == 2) ? 0x2000 : 0x1000;
return m_mapData.c_str()[offset + position];
}

View File

@ -1,6 +1,7 @@
project(minetestmapper CXX)
cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0003 NEW)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
@ -12,8 +13,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g2 -Wall")
if(WIN32)
@ -21,10 +21,9 @@ if(WIN32)
set(BINDIR ".")
set(DOCDIR ".")
else()
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/minetest") # reuse Minetest share dir
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/minetest") # an extra dir. for just one file doesn't seem useful
set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
endif()
set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into")
@ -45,19 +44,16 @@ if(NOT CUSTOM_DOCDIR STREQUAL "")
message(STATUS "Using DOCDIR=${DOCDIR}")
endif()
#set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_package(PkgConfig)
include(FindPackageHandleStandardArgs)
# Libraries: gd
# Libraries: freeimage
find_library(LIBGD_LIBRARY gd)
find_path(LIBGD_INCLUDE_DIR gd.h)
message (STATUS "libgd library: ${LIBGD_LIBRARY}")
message (STATUS "libgd headers: ${LIBGD_INCLUDE_DIR}")
if(NOT LIBGD_LIBRARY OR NOT LIBGD_INCLUDE_DIR)
message(FATAL_ERROR "libgd not found!")
endif(NOT LIBGD_LIBRARY OR NOT LIBGD_INCLUDE_DIR)
find_library(FREEIMAGE_LIBRARY freeimage)
find_path(FREEIMAGE_INCLUDE_DIR FreeImage.h)
message (STATUS "FreeImage library: ${FREEIMAGE_LIBRARY}")
message (STATUS "FreeImage headers: ${FREEIMAGE_INCLUDE_DIR}")
if(NOT FREEIMAGE_LIBRARY OR NOT FREEIMAGE_INCLUDE_DIR)
message(FATAL_ERROR "FreeImage not found!")
endif(NOT FREEIMAGE_LIBRARY OR NOT FREEIMAGE_INCLUDE_DIR)
# Libraries: zlib
@ -69,6 +65,9 @@ if(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR)
message(FATAL_ERROR "zlib not found!")
endif(NOT ZLIB_LIBRARY OR NOT ZLIB_INCLUDE_DIR)
find_package(PkgConfig)
include(FindPackageHandleStandardArgs)
# Libraries: sqlite3
find_library(SQLITE3_LIBRARY sqlite3)
@ -79,85 +78,66 @@ if(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)
message(FATAL_ERROR "sqlite3 not found!")
endif(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)
# Libraries: postgresql
option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
set(USE_POSTGRESQL FALSE)
if(ENABLE_POSTGRESQL)
find_package("PostgreSQL")
if(PostgreSQL_FOUND)
set(USE_POSTGRESQL TRUE)
message(STATUS "PostgreSQL backend enabled")
# This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR
message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIRS}")
include_directories(${PostgreSQL_INCLUDE_DIRS})
else()
message(STATUS "PostgreSQL not found!")
set(PostgreSQL_LIBRARIES "")
endif()
endif(ENABLE_POSTGRESQL)
# Libraries: leveldb
OPTION(ENABLE_LEVELDB "Enable LevelDB backend" TRUE)
set(USE_LEVELDB FALSE)
set(USE_LEVELDB 0)
OPTION(ENABLE_LEVELDB "Enable LevelDB backend")
if(ENABLE_LEVELDB)
find_library(LEVELDB_LIBRARY leveldb)
find_path(LEVELDB_INCLUDE_DIR leveldb/db.h)
find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb)
message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}")
message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}")
if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
set(USE_LEVELDB TRUE)
set(USE_LEVELDB 1)
message(STATUS "LevelDB backend enabled")
include_directories(${LEVELDB_INCLUDE_DIR})
else()
else(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
set(USE_LEVELDB 0)
message(STATUS "LevelDB not found!")
set(LEVELDB_LIBRARY "")
endif()
endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
endif(ENABLE_LEVELDB)
# Libraries: redis
OPTION(ENABLE_REDIS "Enable redis backend" TRUE)
set(USE_REDIS FALSE)
set(USE_REDIS 0)
OPTION(ENABLE_REDIS "Enable redis backend")
if(ENABLE_REDIS)
find_library(REDIS_LIBRARY hiredis)
find_path(REDIS_INCLUDE_DIR hiredis/hiredis.h)
find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis)
message (STATUS "redis library: ${REDIS_LIBRARY}")
message (STATUS "redis headers: ${REDIS_INCLUDE_DIR}")
if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
set(USE_REDIS TRUE)
set(USE_REDIS 1)
message(STATUS "redis backend enabled")
include_directories(${REDIS_INCLUDE_DIR})
else()
else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
set(USE_REDIS 0)
message(STATUS "redis not found!")
set(REDIS_LIBRARY "")
endif()
endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
endif(ENABLE_REDIS)
# Compiling & Linking
include_directories(
"${PROJECT_BINARY_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}"
${SQLITE3_INCLUDE_DIR}
${LIBGD_INCLUDE_DIR}
${FREEIMAGE_INCLUDE_DIR}
${ZLIB_INCLUDE_DIR}
)
configure_file(
"${PROJECT_SOURCE_DIR}/include/cmake_config.h.in"
"${PROJECT_SOURCE_DIR}/cmake_config.h.in"
"${PROJECT_BINARY_DIR}/cmake_config.h"
)
add_definitions ( -DUSE_CMAKE_CONFIG_H )
set(mapper_SRCS
BlockDecoder.cpp
PixelAttributes.cpp
PlayerAttributes.cpp
TileGenerator.cpp
@ -168,10 +148,6 @@ set(mapper_SRCS
db-sqlite3.cpp
)
if(USE_POSTGRESQL)
set(mapper_SRCS ${mapper_SRCS} db-postgresql.cpp)
endif(USE_POSTGRESQL)
if(USE_LEVELDB)
set(mapper_SRCS ${mapper_SRCS} db-leveldb.cpp)
endif(USE_LEVELDB)
@ -187,10 +163,9 @@ add_executable(minetestmapper
target_link_libraries(
minetestmapper
${SQLITE3_LIBRARY}
${PostgreSQL_LIBRARIES}
${LEVELDB_LIBRARY}
${REDIS_LIBRARY}
${LIBGD_LIBRARY}
${FREEIMAGE_LIBRARY}
${ZLIB_LIBRARY}
)
@ -201,9 +176,6 @@ install(FILES "AUTHORS" DESTINATION "${DOCDIR}")
install(FILES "COPYING" DESTINATION "${DOCDIR}")
install(FILES "README.rst" DESTINATION "${DOCDIR}")
install(FILES "colors.txt" DESTINATION "${SHAREDIR}")
if(UNIX)
install(FILES "minetestmapper.6" DESTINATION "${MANDIR}/man6")
endif()
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Overview mapper for Minetest")
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})

View File

@ -4,121 +4,100 @@
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <gd.h>
#include <gdfontmb.h>
#include "Image.h"
#ifndef NDEBUG
#define SIZECHECK(x, y) check_bounds((x), (y), m_width, m_height)
#define SIZECHECK(x, y) do { \
if((x) < 0 || (x) >= m_width) \
throw std::out_of_range("sizecheck x"); \
if((y) < 0 || (y) >= m_height) \
throw std::out_of_range("sizecheck y"); \
} while(0)
#else
#define SIZECHECK(x, y) do {} while(0)
#endif
// ARGB but with inverted alpha
static inline int color2int(Color c)
static inline RGBQUAD color2rgbquad(Color c)
{
u8 a = (255 - c.a) * gdAlphaMax / 255;
return (a << 24) | (c.r << 16) | (c.g << 8) | c.b;
}
static inline Color int2color(int c)
{
Color c2;
u8 a;
c2.b = c & 0xff;
c2.g = (c >> 8) & 0xff;
c2.r = (c >> 16) & 0xff;
a = (c >> 24) & 0xff;
c2.a = 255 - (a*255 / gdAlphaMax);
RGBQUAD c2;
c2.rgbRed = c.r;
c2.rgbGreen = c.g;
c2.rgbBlue = c.b;
return c2;
}
static inline void check_bounds(int x, int y, int width, int height)
static inline Color rgbquad2color(RGBQUAD c)
{
if(x < 0 || x >= width) {
std::ostringstream oss;
oss << "Access outside image bounds (x), 0 < "
<< x << " < " << width << " is false.";
throw std::out_of_range(oss.str());
}
if(y < 0 || y >= height) {
std::ostringstream oss;
oss << "Access outside image bounds (y), 0 < "
<< y << " < " << height << " is false.";
throw std::out_of_range(oss.str());
}
return Color(c.rgbRed, c.rgbGreen, c.rgbBlue);
}
Image::Image(int width, int height) :
m_width(width), m_height(height), m_image(NULL)
{
m_image = gdImageCreateTrueColor(m_width, m_height);
// FIXME: This works because minetestmapper only creates one image
FreeImage_Initialise();
printf("Using FreeImage v%s\n", FreeImage_GetVersion());
m_image = FreeImage_Allocate(width, height, 24);
}
Image::~Image()
{
gdImageDestroy(m_image);
FreeImage_Unload(m_image);
}
void Image::setPixel(int x, int y, const Color &c)
{
SIZECHECK(x, y);
m_image->tpixels[y][x] = color2int(c);
RGBQUAD col = color2rgbquad(c);
FreeImage_SetPixelColor(m_image, x, y, &col);
}
Color Image::getPixel(int x, int y)
{
SIZECHECK(x, y);
return int2color(m_image->tpixels[y][x]);
#ifndef NDEBUG
if(x < 0 || x > m_width || y < 0 || y > m_height)
throw std::out_of_range("sizecheck");
#endif
RGBQUAD c;
FreeImage_GetPixelColor(m_image, x, y, &c);
return rgbquad2color(c);
}
void Image::drawLine(int x1, int y1, int x2, int y2, const Color &c)
{
SIZECHECK(x1, y1);
SIZECHECK(x2, y2);
gdImageLine(m_image, x1, y1, x2, y2, color2int(c));
// TODO
}
void Image::drawText(int x, int y, const std::string &s, const Color &c)
{
SIZECHECK(x, y);
gdImageString(m_image, gdFontGetMediumBold(), x, y, (unsigned char*) s.c_str(), color2int(c));
// TODO
}
void Image::drawFilledRect(int x, int y, int w, int h, const Color &c)
{
SIZECHECK(x, y);
SIZECHECK(x + w - 1, y + h - 1);
gdImageFilledRectangle(m_image, x, y, x + w - 1, y + h - 1, color2int(c));
RGBQUAD col = color2rgbquad(c);
for(int xx = 0; xx < w; xx++)
for(int yy = 0; yy < h; yy++)
FreeImage_SetPixelColor(m_image, x + xx, y + yy, &col);
}
void Image::drawCircle(int x, int y, int diameter, const Color &c)
{
SIZECHECK(x, y);
gdImageArc(m_image, x, y, diameter, diameter, 0, 360, color2int(c));
// TODO
}
void Image::save(const std::string &filename)
{
#if (GD_MAJOR_VERSION == 2 && GD_MINOR_VERSION == 1 && GD_RELEASE_VERSION >= 1) || (GD_MAJOR_VERSION == 2 && GD_MINOR_VERSION > 1) || GD_MAJOR_VERSION > 2
const char *f = filename.c_str();
if (gdSupportsFileType(f, 1) == GD_FALSE)
throw std::runtime_error("Image format not supported by gd");
if (gdImageFile(m_image, f) == GD_FALSE)
throw std::runtime_error("Error saving image");
#else
if (filename.compare(filename.length() - 4, 4, ".png") != 0)
throw std::runtime_error("Only PNG is supported");
FILE *f = fopen(filename.c_str(), "wb");
if (!f) {
std::ostringstream oss;
oss << "Error opening image file: " << std::strerror(errno);
throw std::runtime_error(oss.str());
}
gdImagePng(m_image, f);
fclose(f);
#endif
FreeImage_Save(FIF_PNG, m_image, filename.c_str()); // other formats?
}

View File

@ -3,14 +3,12 @@
#include "types.h"
#include <string>
#include <gd.h>
#include <FreeImage.h>
struct Color {
Color() : r(0), g(0), b(0), a(0) {};
Color(u8 r, u8 g, u8 b) : r(r), g(g), b(b), a(255) {};
Color(u8 r, u8 g, u8 b, u8 a) : r(r), g(g), b(b), a(a) {};
inline Color noAlpha() const { return Color(r, g, b); }
u8 r, g, b, a;
};
@ -31,7 +29,7 @@ private:
Image(const Image&);
int m_width, m_height;
gdImagePtr m_image;
FIBITMAP *m_image;
};
#endif // IMAGE_HEADER

View File

@ -7,14 +7,17 @@
* =====================================================================
*/
#include "PixelAttributes.h"
#include <cstdlib>
#include <cstring>
#include "PixelAttributes.h"
using namespace std;
PixelAttributes::PixelAttributes():
m_width(0)
{
for (size_t i = 0; i < LineCount; ++i) {
m_pixelAttributes[i] = nullptr;
m_pixelAttributes[i] = 0;
}
}
@ -44,9 +47,9 @@ void PixelAttributes::scroll()
void PixelAttributes::freeAttributes()
{
for (size_t i = 0; i < LineCount; ++i) {
if (m_pixelAttributes[i] != nullptr) {
if (m_pixelAttributes[i] != 0) {
delete[] m_pixelAttributes[i];
m_pixelAttributes[i] = nullptr;
m_pixelAttributes[i] = 0;
}
}
}

View File

@ -1,126 +1,66 @@
/*
* =====================================================================
* Version: 1.0
* Created: 01.09.2012 14:38:05
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#include <dirent.h>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <dirent.h>
#include <unistd.h> // for usleep
#include <sqlite3.h>
#include "config.h"
#include "PlayerAttributes.h"
#include "util.h"
using namespace std;
PlayerAttributes::PlayerAttributes(const std::string &worldDir)
PlayerAttributes::PlayerAttributes(const std::string &sourceDirectory)
{
std::ifstream ifs((worldDir + "world.mt").c_str());
if (!ifs.good())
throw std::runtime_error("Failed to read world.mt");
std::string backend = read_setting_default("player_backend", ifs, "files");
ifs.close();
if (backend == "files")
readFiles(worldDir + "players");
else if (backend == "sqlite3")
readSqlite(worldDir + "players.sqlite");
else
throw std::runtime_error(((std::string) "Unknown player backend: ") + backend);
}
void PlayerAttributes::readFiles(const std::string &playersPath)
{
string playersPath = sourceDirectory + "players";
DIR *dir;
dir = opendir (playersPath.c_str());
if (dir == NULL)
if (dir == NULL) {
return;
}
struct dirent *ent;
while ((ent = readdir (dir)) != NULL) {
if (ent->d_name[0] == '.')
if (ent->d_name[0] == '.') {
continue;
}
string path = playersPath + PATH_SEPARATOR + ent->d_name;
ifstream in(path.c_str());
if(!in.good())
continue;
string name, position;
name = read_setting("name", in);
in.seekg(0);
position = read_setting("position", in);
ifstream in;
in.open(path.c_str(), ifstream::in);
string buffer;
string name;
string position;
while (getline(in, buffer)) {
if (buffer.find("name = ") == 0) {
name = buffer.substr(7);
}
else if (buffer.find("position = ") == 0) {
position = buffer.substr(12, buffer.length() - 13);
}
}
char comma;
Player player;
istringstream iss(position);
char tmp;
iss >> tmp; // '('
iss >> player.x;
iss >> tmp; // ','
iss >> player.y;
iss >> tmp; // ','
iss >> player.z;
iss >> tmp; // ')'
if(tmp != ')')
continue;
istringstream positionStream(position, istringstream::in);
positionStream >> player.x;
positionStream >> comma;
positionStream >> player.y;
positionStream >> comma;
positionStream >> player.z;
player.name = name;
player.x /= 10.0;
player.y /= 10.0;
player.z /= 10.0;
m_players.push_back(player);
}
closedir(dir);
}
/**********/
#define SQLRES(f, good) \
result = (sqlite3_##f); \
if (result != good) { \
throw std::runtime_error(sqlite3_errmsg(db));\
}
#define SQLOK(f) SQLRES(f, SQLITE_OK)
void PlayerAttributes::readSqlite(const std::string &db_name)
{
int result;
sqlite3 *db;
sqlite3_stmt *stmt_get_player_pos;
SQLOK(open_v2(db_name.c_str(), &db, SQLITE_OPEN_READONLY |
SQLITE_OPEN_PRIVATECACHE, 0))
SQLOK(prepare_v2(db,
"SELECT name, posX, posY, posZ FROM player",
-1, &stmt_get_player_pos, NULL))
while ((result = sqlite3_step(stmt_get_player_pos)) != SQLITE_DONE) {
if (result == SQLITE_BUSY) { // Wait some time and try again
usleep(10000);
} else if (result != SQLITE_ROW) {
throw std::runtime_error(sqlite3_errmsg(db));
}
Player player;
const unsigned char *name_ = sqlite3_column_text(stmt_get_player_pos, 0);
player.name = std::string(reinterpret_cast<const char*>(name_));
player.x = sqlite3_column_double(stmt_get_player_pos, 1);
player.y = sqlite3_column_double(stmt_get_player_pos, 2);
player.z = sqlite3_column_double(stmt_get_player_pos, 3);
player.x /= 10.0;
player.y /= 10.0;
player.z /= 10.0;
m_players.push_back(player);
}
sqlite3_finalize(stmt_get_player_pos);
sqlite3_close(db);
}
/**********/
PlayerAttributes::Players::iterator PlayerAttributes::begin()
{
return m_players.begin();

38
PlayerAttributes.h Normal file
View File

@ -0,0 +1,38 @@
/*
* =====================================================================
* Version: 1.0
* Created: 01.09.2012 14:38:08
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#ifndef PLAYERATTRIBUTES_H_D7THWFVV
#define PLAYERATTRIBUTES_H_D7THWFVV
#include <list>
#include <string>
struct Player
{
std::string name;
double x;
double y;
double z;
}; /* ----- end of struct Player ----- */
class PlayerAttributes
{
public:
typedef std::list<Player> Players;
PlayerAttributes(const std::string &sourceDirectory);
Players::iterator begin();
Players::iterator end();
private:
Players m_players;
}; /* ----- end of class PlayerAttributes ----- */
#endif /* end of include guard: PLAYERATTRIBUTES_H_D7THWFVV */

View File

@ -10,26 +10,15 @@ This version is both faster and provides more features than the now deprecated P
Requirements
------------
* libgd
* FreeImage
* sqlite3
* LevelDB (optional, set ENABLE_LEVELDB=1 in CMake to enable)
* hiredis library (optional, set ENABLE_REDIS=1 in CMake to enable)
* Postgres libraries (optional, set ENABLE_POSTGRES=1 in CMake to enable)
* leveldb (optional, set ENABLE_LEVELDB=1 in CMake to enable leveldb support)
* hiredis (optional, set ENABLE_REDIS=1 in CMake to enable redis support)
e.g. on Debian:
^^^^^^^^^^^^^^^
sudo apt-get install libgd-dev libsqlite3-dev libleveldb-dev libhiredis-dev libpq-dev
Windows
^^^^^^^
Minetestmapper for Windows can be downloaded here: https://github.com/minetest/minetestmapper/releases
After extracting the archive, minetestmapper can be invoked from cmd.exe:
::
cd C:\Users\yourname\Desktop\example\path
minetestmapper.exe --help
sudo apt-get install libgd-dev libsqlite3-dev libleveldb-dev libhiredis-dev
Compilation
-----------
@ -54,38 +43,32 @@ Parameters
^^^^^^^^^^
bgcolor:
Background color of image, e.g. ``--bgcolor '#ffffff'``
Background color of image, e.g. ``--bgcolor #ffffff``
scalecolor:
Color of scale marks and text, e.g. ``--scalecolor '#000000'``
Color of scale, e.g. ``--scalecolor #000000``
playercolor:
Color of player indicators, e.g. ``--playercolor '#ff0000'``
Color of player indicators, e.g. ``--playercolor #ff0000``
origincolor:
Color of origin indicator, e.g. ``--origincolor '#ff0000'``
Color of origin indicator, e.g. ``--origincolor #ff0000``
drawscale:
Draw scale(s) with tick marks and numbers, ``--drawscale``
Draw tick marks, ``--drawscale``
drawplayers:
Draw player indicators with name, ``--drawplayers``
Draw player indicators, ``--drawplayers``
draworigin:
Draw origin indicator, ``--draworigin``
drawalpha:
Allow nodes to be drawn with transparency (e.g. water), ``--drawalpha``
extent:
Don't output any imagery, just print the extent of the full map, ``--extent``
Allow nodes to be drawn with transparency, ``--drawalpha``
noshading:
Don't draw shading on nodes, ``--noshading``
noemptyimage:
Don't output anything when the image would be empty, ``--noemptyimage``
min-y:
Don't draw nodes below this y value, e.g. ``--min-y -25``
@ -93,21 +76,16 @@ max-y:
Don't draw nodes above this y value, e.g. ``--max-y 75``
backend:
Override auto-detected map backend; supported: *sqlite3*, *leveldb*, *redis*, *postgresql*, e.g. ``--backend leveldb``
Use specific map backend, supported: *sqlite3*, *leveldb*, *redis*, e.g. ``--backend leveldb``
geometry:
Limit area to specific geometry (*x:z+w+h* where x and z specify the lower left corner), e.g. ``--geometry -800:-800+1600+1600``
Limit area to specific geometry (*x:y+w+h* where x and y specify the lower left corner), e.g. ``--geometry -800:-800+1600+1600``
zoom:
Apply zoom to drawn nodes by enlarging them to n*n squares, e.g. ``--zoom 4``
"Zoom" the image by using more than one pixel per node, e.g. ``--zoom 4``
colors:
Override auto-detected path to colors.txt, e.g. ``--colors ../minetest/mycolors.txt``
Forcefully set path to colors.txt file (it's autodetected otherwise), e.g. ``--colors ../minetest/mycolors.txt``
scales:
Draw scales on specified image edges (letters *t b l r* meaning top, bottom, left and right), e.g. ``--scales tbr``
exhaustive:
| Select if database should be traversed exhaustively or using range queries, available: *never*, *y*, *full*, *auto*
| Defaults to *auto*. You shouldn't need to change this, but doing so can improve rendering times on large maps.
| For these optimizations to work it is important that you set ``min-y`` and ``max-y`` when you don't care about the world below e.g. -60 and above 1000 nodes.

View File

@ -1,24 +1,18 @@
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <cassert>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <cstring>
#include <vector>
#include <cmath>
#include "TileGenerator.h"
#include "config.h"
#include "PlayerAttributes.h"
#include "BlockDecoder.h"
#include "TileGenerator.h"
#include "ZlibDecompressor.h"
#include "util.h"
#include "db-sqlite3.h"
#if USE_POSTGRESQL
#include "db-postgresql.h"
#endif
#if USE_LEVELDB
#include "db-leveldb.h"
#endif
@ -28,16 +22,9 @@
using namespace std;
template<typename T>
static inline T mymax(T a, T b)
static inline uint16_t readU16(const unsigned char *data)
{
return (a > b) ? a : b;
}
template<typename T>
static inline T mymin(T a, T b)
{
return (a > b) ? b : a;
return data[0] << 8 | data[1];
}
// rounds n (away from 0) to a multiple of f while preserving the sign of n
@ -52,9 +39,27 @@ static int round_multiple_nosign(int n, int f)
return sign * (abs_n + f - (abs_n % f));
}
static inline unsigned int colorSafeBounds (int channel)
static int readBlockContent(const unsigned char *mapData, int version, int datapos)
{
return mymin(mymax(channel, 0), 255);
if (version >= 24) {
size_t index = datapos << 1;
return (mapData[index] << 8) | mapData[index + 1];
} else if (version >= 20) {
if (mapData[datapos] <= 0x80)
return mapData[datapos];
else
return (int(mapData[datapos]) << 4) | (int(mapData[datapos + 0x2000]) >> 4);
}
std::ostringstream oss;
oss << "Unsupported map version " << version;
throw std::runtime_error(oss.str());
}
static inline int colorSafeBounds(int color)
{
color = (color > 255) ? 255 : color;
color = (color < 0) ? 0 : color;
return color;
}
static Color mixColors(Color a, Color b)
@ -81,7 +86,6 @@ TileGenerator::TileGenerator():
m_drawScale(false),
m_drawAlpha(false),
m_shading(true),
m_dontWriteEmpty(false),
m_backend(""),
m_xBorder(0),
m_yBorder(0),
@ -91,13 +95,12 @@ TileGenerator::TileGenerator():
m_xMax(INT_MIN),
m_zMin(INT_MAX),
m_zMax(INT_MIN),
m_yMin(INT16_MIN),
m_yMax(INT16_MAX),
m_yMin(-30000),
m_yMax(30000),
m_geomX(-2048),
m_geomY(-2048),
m_geomX2(2048),
m_geomY2(2048),
m_exhaustiveSearch(EXH_AUTO),
m_zoom(1),
m_scales(SCALE_LEFT | SCALE_TOP)
{
@ -187,7 +190,6 @@ void TileGenerator::setBackend(std::string backend)
void TileGenerator::setGeometry(int x, int y, int w, int h)
{
assert(w > 0 && h > 0);
m_geomX = round_multiple_nosign(x, 16) / 16;
m_geomY = round_multiple_nosign(y, 16) / 16;
m_geomX2 = round_multiple_nosign(x + w, 16) / 16;
@ -197,20 +199,11 @@ void TileGenerator::setGeometry(int x, int y, int w, int h)
void TileGenerator::setMinY(int y)
{
m_yMin = y;
if (m_yMin > m_yMax)
std::swap(m_yMin, m_yMax);
}
void TileGenerator::setMaxY(int y)
{
m_yMax = y;
if (m_yMin > m_yMax)
std::swap(m_yMin, m_yMax);
}
void TileGenerator::setExhaustiveSearch(int mode)
{
m_exhaustiveSearch = mode;
}
void TileGenerator::parseColorsFile(const std::string &fileName)
@ -222,32 +215,6 @@ void TileGenerator::parseColorsFile(const std::string &fileName)
parseColorsStream(in);
}
void TileGenerator::printGeometry(const std::string &input)
{
string input_path = input;
if (input_path[input.length() - 1] != PATH_SEPARATOR) {
input_path += PATH_SEPARATOR;
}
setExhaustiveSearch(EXH_NEVER);
openDb(input_path);
loadBlocks();
std::cout << "Map extent: "
<< m_xMin*16 << ":" << m_zMin*16
<< "+" << (m_xMax - m_xMin+1)*16
<< "+" << (m_zMax - m_zMin+1)*16
<< std::endl;
closeDatabase();
}
void TileGenerator::setDontWriteEmpty(bool f)
{
m_dontWriteEmpty = f;
}
void TileGenerator::generate(const std::string &input, const std::string &output)
{
string input_path = input;
@ -255,17 +222,8 @@ void TileGenerator::generate(const std::string &input, const std::string &output
input_path += PATH_SEPARATOR;
}
if (m_dontWriteEmpty) // FIXME: possible too, just needs to be done differently
setExhaustiveSearch(EXH_NEVER);
openDb(input_path);
loadBlocks();
if (m_dontWriteEmpty && m_positions.empty())
{
closeDatabase();
return;
}
createImage();
renderMap();
closeDatabase();
@ -284,9 +242,9 @@ void TileGenerator::generate(const std::string &input, const std::string &output
void TileGenerator::parseColorsStream(std::istream &in)
{
char line[512];
char line[128];
while (in.good()) {
in.getline(line, sizeof(line));
in.getline(line, 128);
for(char *p = line; *p; p++) {
if(*p != '#')
@ -297,54 +255,34 @@ void TileGenerator::parseColorsStream(std::istream &in)
if(strlen(line) == 0)
continue;
char name[128 + 1] = {0};
char name[64];
unsigned int r, g, b, a, t;
a = 255;
t = 0;
int items = sscanf(line, "%128s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
int items = sscanf(line, "%64s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
if(items < 4) {
std::cerr << "Failed to parse color entry '" << line << "'" << std::endl;
continue;
}
ColorEntry color(r, g, b, a, t);
ColorEntry color = ColorEntry(r, g, b, a, t);
m_colorMap[name] = color;
}
}
std::set<std::string> TileGenerator::getSupportedBackends()
{
std::set<std::string> r;
r.insert("sqlite3");
#if USE_POSTGRESQL
r.insert("postgresql");
#endif
#if USE_LEVELDB
r.insert("leveldb");
#endif
#if USE_REDIS
r.insert("redis");
#endif
return r;
}
void TileGenerator::openDb(const std::string &input)
{
std::string backend = m_backend;
if (backend == "") {
std::ifstream ifs(input + "/world.mt");
if(backend == "") {
std::ifstream ifs((input + "/world.mt").c_str());
if(!ifs.good())
throw std::runtime_error("Failed to open world.mt");
backend = read_setting_default("backend", ifs, "sqlite3");
throw std::runtime_error("Failed to read world.mt");
backend = get_setting("backend", ifs);
ifs.close();
}
if(backend == "sqlite3")
m_db = new DBSQLite3(input);
#if USE_POSTGRESQL
else if(backend == "postgresql")
m_db = new DBPostgreSQL(input);
#endif
#if USE_LEVELDB
else if(backend == "leveldb")
m_db = new DBLevelDB(input);
@ -355,33 +293,6 @@ void TileGenerator::openDb(const std::string &input)
#endif
else
throw std::runtime_error(((std::string) "Unknown map backend: ") + backend);
// Determine how we're going to traverse the database (heuristic)
if (m_exhaustiveSearch == EXH_AUTO) {
using u64 = uint64_t;
u64 y_range = (m_yMax / 16 + 1) - (m_yMin / 16);
u64 blocks = (u64)(m_geomX2 - m_geomX) * y_range * (u64)(m_geomY2 - m_geomY);
#ifndef NDEBUG
std::cout << "Heuristic parameters:"
<< " preferRangeQueries()=" << m_db->preferRangeQueries()
<< " y_range=" << y_range << " blocks=" << blocks << std::endl;
#endif
if (m_db->preferRangeQueries())
m_exhaustiveSearch = EXH_NEVER;
else if (blocks < 200000)
m_exhaustiveSearch = EXH_FULL;
else if (y_range < 42)
m_exhaustiveSearch = EXH_Y;
else
m_exhaustiveSearch = EXH_NEVER;
} else if (m_exhaustiveSearch == EXH_FULL || m_exhaustiveSearch == EXH_Y) {
if (m_db->preferRangeQueries()) {
std::cerr << "Note: The current database backend supports efficient "
"range queries, forcing exhaustive search should always result "
" in worse performance." << std::endl;
}
}
assert(m_exhaustiveSearch != EXH_AUTO);
}
void TileGenerator::closeDatabase()
@ -392,267 +303,240 @@ void TileGenerator::closeDatabase()
void TileGenerator::loadBlocks()
{
const int16_t yMax = m_yMax / 16 + 1;
std::vector<BlockPos> vec = m_db->getBlockPos();
for (std::vector<BlockPos>::iterator it = vec.begin(); it != vec.end(); ++it) {
BlockPos pos = *it;
// Check that it's in geometry (from --geometry option)
if (pos.x < m_geomX || pos.x >= m_geomX2 || pos.z < m_geomY || pos.z >= m_geomY2)
continue;
// Check that it's between --min-y and --max-y
if (pos.y * 16 < m_yMin || pos.y * 16 > m_yMax)
continue;
// Adjust minimum and maximum positions to the nearest block
if (pos.x < m_xMin)
m_xMin = pos.x;
if (pos.x > m_xMax)
m_xMax = pos.x;
if (m_exhaustiveSearch == EXH_NEVER || m_exhaustiveSearch == EXH_Y) {
std::vector<BlockPos> vec = m_db->getBlockPos(
BlockPos(m_geomX, m_yMin / 16, m_geomY),
BlockPos(m_geomX2, yMax, m_geomY2)
);
for (auto pos : vec) {
assert(pos.x >= m_geomX && pos.x < m_geomX2);
assert(pos.y >= m_yMin / 16 && pos.y < yMax);
assert(pos.z >= m_geomY && pos.z < m_geomY2);
// Adjust minimum and maximum positions to the nearest block
if (pos.x < m_xMin)
m_xMin = pos.x;
if (pos.x > m_xMax)
m_xMax = pos.x;
if (pos.z < m_zMin)
m_zMin = pos.z;
if (pos.z > m_zMax)
m_zMax = pos.z;
m_positions[pos.z].emplace(pos.x);
}
#ifndef NDEBUG
int count = 0;
for (const auto &it : m_positions)
count += it.second.size();
std::cout << "Loaded " << count
<< " positions (across Z: " << m_positions.size() << ") for rendering" << std::endl;
#endif
if (pos.z < m_zMin)
m_zMin = pos.z;
if (pos.z > m_zMax)
m_zMax = pos.z;
m_positions.push_back(std::pair<int, int>(pos.x, pos.z));
}
m_positions.sort();
m_positions.unique();
}
void TileGenerator::createImage()
{
const int scale_d = 40; // pixels reserved for a scale
if(!m_drawScale)
m_scales = 0;
// If a geometry is explicitly set, set the bounding box to the requested geometry
// instead of cropping to the content. This way we will always output a full tile
// of the correct size.
if (m_geomX > -2048 && m_geomX2 < 2048)
{
m_xMin = m_geomX;
m_xMax = m_geomX2-1;
}
if (m_geomY > -2048 && m_geomY2 < 2048)
{
m_zMin = m_geomY;
m_zMax = m_geomY2-1;
}
m_mapWidth = (m_xMax - m_xMin + 1) * 16;
m_mapHeight = (m_zMax - m_zMin + 1) * 16;
m_xBorder = (m_scales & SCALE_LEFT) ? scale_d : 0;
m_yBorder = (m_scales & SCALE_TOP) ? scale_d : 0;
if(m_drawScale) {
m_xBorder = (m_scales & SCALE_LEFT) ? 40 : 0;
m_yBorder = (m_scales & SCALE_TOP) ? 40 : 0;
}
m_blockPixelAttributes.setWidth(m_mapWidth);
int image_width, image_height;
image_width = (m_mapWidth * m_zoom) + m_xBorder;
image_width += (m_scales & SCALE_RIGHT) ? scale_d : 0;
image_width += m_drawScale && (m_scales & SCALE_RIGHT) ? 40 : 0;
image_height = (m_mapHeight * m_zoom) + m_yBorder;
image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 0;
image_height += m_drawScale && (m_scales & SCALE_BOTTOM) ? 40 : 0;
if(image_width > 4096 || image_height > 4096) {
if(image_width > 4096 || image_height > 4096)
std::cerr << "Warning: The width or height of the image to be created exceeds 4096 pixels!"
<< " (Dimensions: " << image_width << "x" << image_height << ")"
<< std::endl;
}
m_image = new Image(image_width, image_height);
m_image->drawFilledRect(0, 0, image_width, image_height, m_bgColor); // Background
}
void TileGenerator::renderMap()
{
BlockDecoder blk;
const int16_t yMax = m_yMax / 16 + 1;
std::list<int> zlist = getZValueList();
for (std::list<int>::iterator zPosition = zlist.begin(); zPosition != zlist.end(); ++zPosition) {
int zPos = *zPosition;
std::map<int16_t, BlockList> blocks;
m_db->getBlocksOnZ(blocks, zPos);
for (std::list<std::pair<int, int> >::const_iterator position = m_positions.begin(); position != m_positions.end(); ++position) {
if (position->second != zPos)
continue;
auto renderSingle = [&] (int16_t xPos, int16_t zPos, BlockList &blockStack) {
m_readPixels.reset();
m_readInfo.reset();
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
m_color[i][j] = m_bgColor; // This will be drawn by renderMapBlockBottom() for y-rows with only 'air', 'ignore' or unknown nodes if --drawalpha is used
m_color[i][j].a = 0; // ..but set alpha to 0 to tell renderMapBlock() not to use this color to mix a shade
m_thickness[i][j] = 0;
for (int i = 0; i < 16; ++i) {
m_readPixels[i] = 0;
m_readInfo[i] = 0;
}
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
m_color[i][j] = m_bgColor; // This will be drawn by renderMapBlockBottom() for y-rows with only 'air', 'ignore' or unknown nodes if --drawalpha is used
m_color[i][j].a = 0; // ..but set alpha to 0 to tell renderMapBlock() not to use this color to mix a shade
m_thickness[i][j] = 0;
}
}
int xPos = position->first;
blocks[xPos].sort();
const BlockList &blockStack = blocks[xPos];
for (BlockList::const_iterator it = blockStack.begin(); it != blockStack.end(); ++it) {
const BlockPos &pos = it->first;
const unsigned char *data = it->second.c_str();
size_t length = it->second.length();
uint8_t version = data[0];
//uint8_t flags = data[1];
size_t dataOffset = 0;
if (version >= 22)
dataOffset = 4;
else
dataOffset = 2;
ZlibDecompressor decompressor(data, length);
decompressor.setSeekPos(dataOffset);
ustring mapData = decompressor.decompress();
ustring mapMetadata = decompressor.decompress();
dataOffset = decompressor.seekPos();
// Skip unused data
if (version <= 21)
dataOffset += 2;
if (version == 23)
dataOffset += 1;
if (version == 24) {
uint8_t ver = data[dataOffset++];
if (ver == 1) {
uint16_t num = readU16(data + dataOffset);
dataOffset += 2;
dataOffset += 10 * num;
}
}
// Skip unused static objects
dataOffset++; // Skip static object version
int staticObjectCount = readU16(data + dataOffset);
dataOffset += 2;
for (int i = 0; i < staticObjectCount; ++i) {
dataOffset += 13;
uint16_t dataSize = readU16(data + dataOffset);
dataOffset += dataSize + 2;
}
dataOffset += 4; // Skip timestamp
m_blockAirId = -1;
m_blockIgnoreId = -1;
m_nameMap.clear();
// Read mapping
if (version >= 22) {
dataOffset++; // mapping version
uint16_t numMappings = readU16(data + dataOffset);
dataOffset += 2;
for (int i = 0; i < numMappings; ++i) {
uint16_t nodeId = readU16(data + dataOffset);
dataOffset += 2;
uint16_t nameLen = readU16(data + dataOffset);
dataOffset += 2;
string name = string(reinterpret_cast<const char *>(data) + dataOffset, nameLen);
if (name == "air")
m_blockAirId = nodeId;
else if (name == "ignore")
m_blockIgnoreId = nodeId;
else
m_nameMap[nodeId] = name;
dataOffset += nameLen;
}
// Skip block if made of only air or ignore nodes
if (m_nameMap.empty())
continue;
}
// Node timers
if (version >= 25) {
dataOffset++;
uint16_t numTimers = readU16(data + dataOffset);
dataOffset += 2;
dataOffset += numTimers * 10;
}
renderMapBlock(mapData, pos, version);
bool allRead = true;
for (int i = 0; i < 16; ++i) {
if (m_readPixels[i] != 0xffff)
allRead = false;
}
if (allRead)
break;
}
bool allRead = true;
for (int i = 0; i < 16; ++i) {
if (m_readPixels[i] != 0xffff)
allRead = false;
}
if (!allRead)
renderMapBlockBottom(blockStack.begin()->first);
}
for (const auto &it : blockStack) {
const BlockPos pos = it.first;
assert(pos.x == xPos && pos.z == zPos);
assert(pos.y >= m_yMin / 16 && pos.y < yMax);
blk.reset();
blk.decode(it.second);
renderMapBlock(blk, pos);
// Exit out if all pixels for this MapBlock are covered
if (m_readPixels.full())
break;
}
if (!m_readPixels.full())
renderMapBlockBottom(blockStack.begin()->first);
};
auto postRenderRow = [&] (int16_t zPos) {
if (m_shading)
if(m_shading)
renderShading(zPos);
};
if (m_exhaustiveSearch == EXH_NEVER) {
for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) {
int16_t zPos = it->first;
for (auto it2 = it->second.rbegin(); it2 != it->second.rend(); ++it2) {
int16_t xPos = *it2;
BlockList blockStack;
m_db->getBlocksOnXZ(blockStack, xPos, zPos, m_yMin / 16, yMax);
blockStack.sort();
renderSingle(xPos, zPos, blockStack);
}
postRenderRow(zPos);
}
} else if (m_exhaustiveSearch == EXH_Y) {
#ifndef NDEBUG
std::cerr << "Exhaustively searching height of "
<< (yMax - (m_yMin / 16)) << " blocks" << std::endl;
#endif
std::vector<BlockPos> positions;
positions.reserve(yMax - (m_yMin / 16));
for (auto it = m_positions.rbegin(); it != m_positions.rend(); ++it) {
int16_t zPos = it->first;
for (auto it2 = it->second.rbegin(); it2 != it->second.rend(); ++it2) {
int16_t xPos = *it2;
positions.clear();
for (int16_t yPos = m_yMin / 16; yPos < yMax; yPos++)
positions.emplace_back(xPos, yPos, zPos);
BlockList blockStack;
m_db->getBlocksByPos(blockStack, positions);
blockStack.sort();
renderSingle(xPos, zPos, blockStack);
}
postRenderRow(zPos);
}
} else if (m_exhaustiveSearch == EXH_FULL) {
#ifndef NDEBUG
std::cerr << "Exhaustively searching "
<< (m_geomX2 - m_geomX) << "x" << (yMax - (m_yMin / 16)) << "x"
<< (m_geomY2 - m_geomY) << " blocks" << std::endl;
#endif
std::vector<BlockPos> positions;
positions.reserve(yMax - (m_yMin / 16));
for (int16_t zPos = m_geomY2 - 1; zPos >= m_geomY; zPos--) {
for (int16_t xPos = m_geomX2 - 1; xPos >= m_geomX; xPos--) {
positions.clear();
for (int16_t yPos = m_yMin / 16; yPos < yMax; yPos++)
positions.emplace_back(xPos, yPos, zPos);
BlockList blockStack;
m_db->getBlocksByPos(blockStack, positions);
blockStack.sort();
renderSingle(xPos, zPos, blockStack);
}
postRenderRow(zPos);
}
}
}
void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos)
void TileGenerator::renderMapBlock(const ustring &mapBlock, const BlockPos &pos, int version)
{
/***/
static bool light_curve_init = false;
static float light_curve[16];
if (!light_curve_init) {
for (u8 i = 0; i < 16; i++)
light_curve[i] = expf((i - 15) / 12.0f);
light_curve_init = true;
}
/***/
auto light_at = [blk] (u8 x, u8 y, u8 z) -> u8 {
return blk.getParam1(x, y, z) >> 4; // night bank
};
static u8 m_light[16][16];
if (blk.isEmpty()) {
for (int z = 0; z < 16; ++z) {
for (int x = 0; x < 16; ++x) {
m_light[z][x] = light_at(x, 0, z);
}
}
return;
}
int xBegin = (pos.x - m_xMin) * 16;
int zBegin = (m_zMax - pos.z) * 16;
const unsigned char *mapData = mapBlock.c_str();
int minY = (pos.y * 16 > m_yMin) ? 0 : m_yMin - pos.y * 16;
int maxY = (pos.y * 16 + 15 < m_yMax) ? 15 : m_yMax - pos.y * 16;
int maxY = (pos.y * 16 < m_yMax) ? 15 : m_yMax - pos.y * 16;
for (int z = 0; z < 16; ++z) {
int imageY = zBegin + 15 - z;
for (int x = 0; x < 16; ++x) {
if (m_readPixels.get(x, z))
if (m_readPixels[z] & (1 << x))
continue;
int imageX = xBegin + x;
for (int y = maxY; y >= minY; --y) {
string name = blk.getNode(x, y, z);
if (name == "") {
if (y == 0) m_light[z][x] = light_at(x, 0, z);
int position = x + (y << 4) + (z << 8);
int content = readBlockContent(mapData, version, position);
if (content == m_blockAirId || content == m_blockIgnoreId)
continue;
NameMap::iterator blockName = m_nameMap.find(content);
if (blockName == m_nameMap.end()) {
std::cerr << "Skipping node with invalid ID." << std::endl;
continue;
}
ColorMap::const_iterator it = m_colorMap.find(name);
if (it == m_colorMap.end()) {
const string &name = blockName->second;
ColorMap::const_iterator color = m_colorMap.find(name);
if (color != m_colorMap.end()) {
const Color c = color->second.to_color();
if (m_drawAlpha) {
// mix with previous color (unless first visible time)
if (m_color[z][x].a == 0)
m_color[z][x] = c;
else
m_color[z][x] = mixColors(m_color[z][x], c);
if(m_color[z][x].a == 0xff) {
// color is opaque at this depth (no point continuing)
setZoomed(imageX, imageY, m_color[z][x]);
m_readPixels[z] |= (1 << x);
m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x];
} else {
// near thickness value to thickness of current node
m_thickness[z][x] = (m_thickness[z][x] + color->second.t) / 2.0;
continue;
}
} else {
setZoomed(imageX, imageY, c);
m_readPixels[z] |= (1 << x);
}
if(!(m_readInfo[z] & (1 << x))) {
m_blockPixelAttributes.attribute(15 - z, xBegin + x).height = pos.y * 16 + y;
m_readInfo[z] |= (1 << x);
}
} else {
m_unknownNodes.insert(name);
continue;
}
Color c = it->second.to_color();
u8 light = (y == 15) ? m_light[z][x] : light_at(x, y+1, z);
if (light < 15) light = mymax(light, light_at(x, y, z));
if (1) {
float l2 = light_curve[light];
c.r = colorSafeBounds(c.r * l2);
c.g = colorSafeBounds(c.g * l2);
c.b = colorSafeBounds(c.b * l2);
} else
c = Color(light * 17, light * 17, light * 17);
if (m_drawAlpha) {
if (m_color[z][x].a == 0)
m_color[z][x] = c; // first visible time, no color mixing
else
m_color[z][x] = mixColors(m_color[z][x], c);
if(m_color[z][x].a < 0xff) {
// near thickness value to thickness of current node
m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2.0;
continue;
}
// color became opaque, draw it
setZoomed(imageX, imageY, m_color[z][x]);
m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x];
} else {
setZoomed(imageX, imageY, c.noAlpha());
}
m_readPixels.set(x, z);
// do this afterwards so we can record height values
// inside transparent nodes (water) too
if (!m_readInfo.get(x, z)) {
m_blockPixelAttributes.attribute(15 - z, xBegin + x).height = pos.y * 16 + y;
m_readInfo.set(x, z);
}
break;
}
}
@ -661,22 +545,21 @@ void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos)
void TileGenerator::renderMapBlockBottom(const BlockPos &pos)
{
if (!m_drawAlpha)
return; // "missing" pixels can only happen with --drawalpha
int xBegin = (pos.x - m_xMin) * 16;
int zBegin = (m_zMax - pos.z) * 16;
for (int z = 0; z < 16; ++z) {
int imageY = zBegin + 15 - z;
for (int x = 0; x < 16; ++x) {
if (m_readPixels.get(x, z))
if (m_readPixels[z] & (1 << x))
continue;
int imageX = xBegin + x;
// set color since it wasn't done in renderMapBlock()
setZoomed(imageX, imageY, m_color[z][x]);
m_readPixels.set(x, z);
m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x];
if (m_drawAlpha) {
// set color in case it wasn't done in renderMapBlock()
setZoomed(imageX, imageY, m_color[z][x]);
m_readPixels[z] |= (1 << x);
m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x];
}
}
}
}
@ -701,11 +584,11 @@ void TileGenerator::renderShading(int zPos)
int y1 = m_blockPixelAttributes.attribute(z, x - 1).height;
int y2 = m_blockPixelAttributes.attribute(z - 1, x).height;
int d = ((y - y1) + (y - y2)) * 12;
if (m_drawAlpha) { // less visible shadow with increasing "thickness"
double t = m_blockPixelAttributes.attribute(z, x).thickness * 1.2;
d *= 1.0 - mymin(t, 255.0) / 255.0;
}
d = mymin(d, 36);
if (d > 36)
d = 36;
// more thickness -> less visible shadows: t=0 -> 100% visible, t=255 -> 0% visible
if (m_drawAlpha)
d = d * ((0xFF - m_blockPixelAttributes.attribute(z, x).thickness) / 255.0);
Color c = m_image->getPixel(getImageX(x), getImageY(imageY));
c.r = colorSafeBounds(c.r + d);
@ -719,97 +602,85 @@ void TileGenerator::renderShading(int zPos)
void TileGenerator::renderScale()
{
const int scale_d = 40; // see createImage()
if (m_scales & SCALE_TOP) {
m_image->drawText(24, 0, "X", m_scaleColor);
for (int i = (m_xMin / 4) * 4; i <= m_xMax; i += 4) {
std::ostringstream buf;
stringstream buf;
buf << i * 16;
int xPos = getImageX(i * 16, true);
if (xPos >= 0) {
m_image->drawText(xPos + 2, 0, buf.str(), m_scaleColor);
m_image->drawLine(xPos, 0, xPos, m_yBorder - 1, m_scaleColor);
}
int xPos = (m_xMin * -16 + i * 16)*m_zoom + m_xBorder;
m_image->drawText(xPos + 2, 0, buf.str(), m_scaleColor);
m_image->drawLine(xPos, 0, xPos, m_yBorder - 1, m_scaleColor);
}
}
if (m_scales & SCALE_LEFT) {
m_image->drawText(2, 24, "Z", m_scaleColor);
for (int i = (m_zMax / 4) * 4; i >= m_zMin; i -= 4) {
std::ostringstream buf;
stringstream buf;
buf << i * 16;
int yPos = getImageY(i * 16 + 1, true);
if (yPos >= 0) {
m_image->drawText(2, yPos, buf.str(), m_scaleColor);
m_image->drawLine(0, yPos, m_xBorder - 1, yPos, m_scaleColor);
}
int yPos = (m_mapHeight - 1 - (i * 16 - m_zMin * 16))*m_zoom + m_yBorder;
m_image->drawText(2, yPos, buf.str(), m_scaleColor);
m_image->drawLine(0, yPos, m_xBorder - 1, yPos, m_scaleColor);
}
}
if (m_scales & SCALE_BOTTOM) {
int xPos = m_xBorder + m_mapWidth*m_zoom - 24 - 8,
yPos = m_yBorder + m_mapHeight*m_zoom + scale_d - 12;
m_image->drawText(xPos, yPos, "X", m_scaleColor);
for (int i = (m_xMin / 4) * 4; i <= m_xMax; i += 4) {
std::ostringstream buf;
stringstream buf;
buf << i * 16;
xPos = getImageX(i * 16, true);
yPos = m_yBorder + m_mapHeight*m_zoom;
if (xPos >= 0) {
m_image->drawText(xPos + 2, yPos, buf.str(), m_scaleColor);
m_image->drawLine(xPos, yPos, xPos, yPos + 39, m_scaleColor);
}
int xPos = (m_xMin * -16 + i * 16)*m_zoom + m_xBorder;
int yPos = m_yBorder + m_mapHeight*m_zoom;
m_image->drawText(xPos + 2, yPos, buf.str(), m_scaleColor);
m_image->drawLine(xPos, yPos, xPos, yPos + 39, m_scaleColor);
}
}
if (m_scales & SCALE_RIGHT) {
int xPos = m_xBorder + m_mapWidth*m_zoom + scale_d - 2 - 8,
yPos = m_yBorder + m_mapHeight*m_zoom - 24 - 12;
m_image->drawText(xPos, yPos, "Z", m_scaleColor);
for (int i = (m_zMax / 4) * 4; i >= m_zMin; i -= 4) {
std::ostringstream buf;
stringstream buf;
buf << i * 16;
xPos = m_xBorder + m_mapWidth*m_zoom;
yPos = getImageY(i * 16 + 1, true);
if (yPos >= 0) {
m_image->drawText(xPos + 2, yPos, buf.str(), m_scaleColor);
m_image->drawLine(xPos, yPos, xPos + 39, yPos, m_scaleColor);
}
int xPos = m_xBorder + m_mapWidth*m_zoom;
int yPos = (m_mapHeight - 1 - (i * 16 - m_zMin * 16))*m_zoom + m_yBorder;
m_image->drawText(xPos + 2, yPos, buf.str(), m_scaleColor);
m_image->drawLine(xPos, yPos, xPos + 39, yPos, m_scaleColor);
}
}
}
void TileGenerator::renderOrigin()
{
if (m_xMin > 0 || m_xMax < 0 ||
m_zMin > 0 || m_zMax < 0)
return;
m_image->drawCircle(getImageX(0, true), getImageY(0, true), 12, m_originColor);
int imageX = (-m_xMin * 16)*m_zoom + m_xBorder;
int imageY = (m_mapHeight - m_zMin * -16)*m_zoom + m_yBorder;
m_image->drawCircle(imageX, imageY, 12, m_originColor);
}
void TileGenerator::renderPlayers(const std::string &inputPath)
{
PlayerAttributes players(inputPath);
for (auto &player : players) {
if (player.x < m_xMin * 16 || player.x > m_xMax * 16 ||
player.z < m_zMin * 16 || player.z > m_zMax * 16)
continue;
if (player.y < m_yMin || player.y > m_yMax)
continue;
int imageX = getImageX(player.x, true),
imageY = getImageY(player.z, true);
for (PlayerAttributes::Players::iterator player = players.begin(); player != players.end(); ++player) {
int imageX = (player->x / 10 - m_xMin * 16)*m_zoom + m_xBorder;
int imageY = (m_mapHeight - (player->z / 10 - m_zMin * 16))*m_zoom + m_yBorder;
m_image->drawFilledRect(imageX - 1, imageY, 3, 1, m_playerColor);
m_image->drawFilledRect(imageX, imageY - 1, 1, 3, m_playerColor);
m_image->drawText(imageX + 2, imageY, player.name, m_playerColor);
m_image->drawCircle(imageX, imageY, 5, m_playerColor);
m_image->drawText(imageX + 2, imageY + 2, player->name, m_playerColor);
}
}
inline std::list<int> TileGenerator::getZValueList() const
{
std::list<int> zlist;
for (std::list<std::pair<int, int> >::const_iterator position = m_positions.begin(); position != m_positions.end(); ++position)
zlist.push_back(position->second);
zlist.sort();
zlist.unique();
zlist.reverse();
return zlist;
}
void TileGenerator::writeImage(const std::string &output)
{
m_image->save(output);
@ -819,24 +690,21 @@ void TileGenerator::writeImage(const std::string &output)
void TileGenerator::printUnknown()
{
if (m_unknownNodes.size() == 0)
return;
std::cerr << "Unknown nodes:" << std::endl;
for (const auto &node : m_unknownNodes)
std::cerr << "\t" << node << std::endl;
if (m_unknownNodes.size() > 0) {
std::cerr << "Unknown nodes:" << std::endl;
for (NameSet::iterator node = m_unknownNodes.begin(); node != m_unknownNodes.end(); ++node) {
std::cerr << *node << std::endl;
}
}
}
inline int TileGenerator::getImageX(int val, bool absolute) const
inline int TileGenerator::getImageX(int val) const
{
if (absolute)
val = (val - m_xMin * 16);
return (m_zoom*val) + m_xBorder;
}
inline int TileGenerator::getImageY(int val, bool absolute) const
inline int TileGenerator::getImageY(int val) const
{
if (absolute)
val = m_mapHeight - (val - m_zMin * 16); // Z axis is flipped on image
return (m_zoom*val) + m_yBorder;
}

View File

@ -2,16 +2,18 @@
#define TILEGENERATOR_HEADER
#include <iosfwd>
#include <map>
#include <set>
#include <list>
#include <config.h>
#if __cplusplus >= 201103L
#include <unordered_map>
#include <unordered_set>
#else
#include <map>
#include <set>
#endif
#include <stdint.h>
#include <string>
#include "PixelAttributes.h"
#include "BlockDecoder.h"
#include "Image.h"
#include "db.h"
#include "types.h"
@ -23,48 +25,30 @@ enum {
SCALE_RIGHT = (1 << 3),
};
enum {
EXH_NEVER, // Always use range queries
EXH_Y, // Exhaustively search Y space, range queries for X/Z
EXH_FULL, // Exhaustively search entire requested geometry
EXH_AUTO, // Automatically pick one of the previous modes
};
struct ColorEntry {
ColorEntry(): r(0), g(0), b(0), a(0), t(0) {};
ColorEntry(uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t t): r(r), g(g), b(b), a(a), t(t) {};
inline Color to_color() const { return Color(r, g, b, a); }
uint8_t r, g, b, a, t;
};
struct BitmapThing { // 16x16 bitmap
inline void reset() {
for (int i = 0; i < 16; ++i)
val[i] = 0;
}
inline bool full() const {
for (int i = 0; i < 16; ++i) {
if (val[i] != 0xffff)
return false;
}
return true;
}
inline void set(unsigned int x, unsigned int z) {
val[z] |= (1 << x);
}
inline bool get(unsigned int x, unsigned int z) {
return !!(val[z] & (1 << x));
}
uint16_t val[16];
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
uint8_t t;
};
class TileGenerator
{
private:
#if __cplusplus >= 201103L
typedef std::unordered_map<std::string, ColorEntry> ColorMap;
typedef std::unordered_map<int, std::string> NameMap;
typedef std::unordered_set<std::string> NameSet;
#else
typedef std::map<std::string, ColorEntry> ColorMap;
typedef std::map<int, std::string> NameMap;
typedef std::set<std::string> NameSet;
#endif
public:
TileGenerator();
@ -72,8 +56,7 @@ public:
void setBgColor(const std::string &bgColor);
void setScaleColor(const std::string &scaleColor);
void setOriginColor(const std::string &originColor);
void setPlayerColor(const std::string &playerColor);
Color parseColor(const std::string &color);
void setPlayerColor(const std::string &playerColor); Color parseColor(const std::string &color);
void setDrawOrigin(bool drawOrigin);
void setDrawPlayers(bool drawPlayers);
void setDrawScale(bool drawScale);
@ -82,16 +65,11 @@ public:
void setGeometry(int x, int y, int w, int h);
void setMinY(int y);
void setMaxY(int y);
void setExhaustiveSearch(int mode);
void parseColorsFile(const std::string &fileName);
void setBackend(std::string backend);
void generate(const std::string &input, const std::string &output);
void setZoom(int zoom);
void setScales(uint flags);
void setDontWriteEmpty(bool f);
void generate(const std::string &input, const std::string &output);
void printGeometry(const std::string &input);
static std::set<std::string> getSupportedBackends();
private:
void parseColorsStream(std::istream &in);
@ -100,7 +78,8 @@ private:
void loadBlocks();
void createImage();
void renderMap();
void renderMapBlock(const BlockDecoder &blk, const BlockPos &pos);
std::list<int> getZValueList() const;
void renderMapBlock(const ustring &mapBlock, const BlockPos &pos, int version);
void renderMapBlockBottom(const BlockPos &pos);
void renderShading(int zPos);
void renderScale();
@ -108,8 +87,8 @@ private:
void renderPlayers(const std::string &inputPath);
void writeImage(const std::string &output);
void printUnknown();
int getImageX(int val, bool absolute=false) const;
int getImageY(int val, bool absolute=false) const;
int getImageX(int val) const;
int getImageY(int val) const;
void setZoomed(int x, int y, Color color);
private:
@ -122,38 +101,35 @@ private:
bool m_drawScale;
bool m_drawAlpha;
bool m_shading;
bool m_dontWriteEmpty;
std::string m_backend;
int m_xBorder, m_yBorder;
DB *m_db;
Image *m_image;
PixelAttributes m_blockPixelAttributes;
/* smallest/largest seen X or Z block coordinate */
int m_xMin;
int m_xMax;
int m_zMin;
int m_zMax;
/* Y limits for rendered area (node units) */
int m_yMin;
int m_yMax;
/* limits for rendered area (block units) */
int16_t m_geomX;
int16_t m_geomY; /* Y in terms of rendered image, Z in the world */
int16_t m_geomX2;
int16_t m_geomY2;
/* */
int m_geomX;
int m_geomY;
int m_geomX2;
int m_geomY2;
int m_mapWidth;
int m_mapHeight;
int m_exhaustiveSearch;
std::map<int16_t, std::set<int16_t>> m_positions; /* indexed by Z, contains X coords */
std::list<std::pair<int, int> > m_positions;
NameMap m_nameMap;
ColorMap m_colorMap;
BitmapThing m_readPixels;
BitmapThing m_readInfo;
uint16_t m_readPixels[16];
uint16_t m_readInfo[16];
NameSet m_unknownNodes;
Color m_color[16][16];
uint8_t m_thickness[16][16];
int m_blockAirId;
int m_blockIgnoreId;
int m_zoom;
uint m_scales;
}; // class TileGenerator

View File

@ -1,14 +1,11 @@
==FILE== mods/dumpnodes/init.lua
local function nd_get_tiles(nd)
return nd.tiles or nd.tile_images
end
local function nd_get_tile(nd, n)
local tile = nd_get_tiles(nd)[n]
if type(tile) == 'table' then
tile = tile.name
if nd.tiles then
return nd.tiles
elseif nd.tile_images then
return nd.tile_images
end
return tile
return nil
end
local function pairs_s(dict)
@ -23,19 +20,19 @@ end
minetest.register_chatcommand("dumpnodes", {
params = "",
description = "",
func = function(player, param)
func = function(plname, param)
local n = 0
local ntbl = {}
for _, nn in pairs_s(minetest.registered_nodes) do
local nd = minetest.registered_nodes[nn]
local prefix, name = nn:match('(.*):(.*)')
if prefix == nil or name == nil then
if prefix == nil or name == nil or prefix == '' or name == '' then
print("ignored(1): " .. nn)
else
if ntbl[prefix] == nil then
ntbl[prefix] = {}
end
ntbl[prefix][name] = true
ntbl[prefix][name] = nd
end
end
local out, err = io.open('nodes.txt', 'wb')
@ -43,17 +40,20 @@ minetest.register_chatcommand("dumpnodes", {
return true, "io.open(): " .. err
end
for _, prefix in pairs_s(ntbl) do
local nodes = ntbl[prefix]
out:write('# ' .. prefix .. '\n')
for _, name in pairs_s(ntbl[prefix]) do
local nn = prefix .. ":" .. name
local nd = minetest.registered_nodes[nn]
if nd.drawtype == 'airlike' or nd_get_tiles(nd) == nil then
print("ignored(2): " .. nn)
else
local tl = nd_get_tile(nd, 1)
tl = (tl .. '^'):match('(.-)^') -- strip modifiers
out:write(nn .. ' ' .. tl .. '\n')
for _, name in pairs_s(nodes) do
local nd = nodes[name]
if nd.drawtype ~= 'airlike' and nd_get_tiles(nd) ~= nil then
local tl = nd_get_tiles(nd)[1]
if type(tl) == 'table' then
tl = tl.name
end
tl = (tl .. '^'):match('(.-)^')
out:write(prefix .. ':' .. name .. ' ' .. tl .. '\n')
n = n + 1
else
print("ignored(2): " .. prefix .. ':' .. name)
end
end
out:write('\n')
@ -65,68 +65,71 @@ minetest.register_chatcommand("dumpnodes", {
==FILE== avgcolor.py
#!/usr/bin/env python
import sys
from math import sqrt
from PIL import Image
def tsum(a, b):
return tuple(sum(e) for e in zip(a, b))
if len(sys.argv) < 2:
print("Prints average color (RGB) of input image")
print("Usage: %s <input>" % sys.argv[0])
exit(1)
inp = Image.open(sys.argv[1]).convert('RGBA')
inp = Image.open(sys.argv[1])
inp = inp.convert('RGBA')
ind = inp.load()
cl = ([], [], [])
cl = (0, 0, 0)
counted = 0
for x in range(inp.size[0]):
for y in range(inp.size[1]):
px = ind[x, y]
if px[3] < 128: continue # alpha
cl[0].append(px[0]**2)
cl[1].append(px[1]**2)
cl[2].append(px[2]**2)
if px[3] < 128:
continue
cl = tsum(cl, px[:3])
counted = counted + 1
if len(cl[0]) == 0:
print("Didn't find average color for %s" % sys.argv[1], file=sys.stderr)
if counted == 0:
sys.stderr.write("didn't find avg. color for %s\n" % sys.argv[1])
print("0 0 0")
else:
cl = tuple(sqrt(sum(x)/len(x)) for x in cl)
print("%d %d %d" % cl)
exit(0)
cl = tuple(int(n / counted) for n in cl)
print("%d %d %d" % cl)
==SCRIPT==
#!/bin/bash -e
AVGCOLOR_PATH=/path/to/avgcolor.py
GAME_PATH=/path/to/minetest_game
MODS_PATH= # path to "mods" folder, only set if you have loaded mods
MTGAME_PATH=/path/to/minetest_game
NODESTXT_PATH=./nodes.txt
COLORSTXT_PATH=./colors.txt
while read -r line; do
set -- junk $line; shift
set -- junk $line
shift
if [[ -z "$1" || $1 == "#" ]]; then
echo "$line"; continue
echo $line
continue
fi
tex=$(find $GAME_PATH -type f -name "$2")
[[ -z "$tex" && -n "$MODS_PATH" ]] && tex=$(find $MODS_PATH -type f -name "$2")
tex=$(find $MTGAME_PATH -type f -name "$2")
if [ -z "$tex" ]; then
echo "skip $1: texture not found" >&2
continue
fi
echo "$1" $(python $AVGCOLOR_PATH "$tex")
echo $1 $(python $AVGCOLOR_PATH "$tex")
echo "ok $1" >&2
done < $NODESTXT_PATH > $COLORSTXT_PATH
# Use nicer colors for water and lava:
sed -re 's/^default:((river_)?water_(flowing|source)) [0-9 ]+$/default:\1 39 66 106 128 224/g' $COLORSTXT_PATH -i
sed -re 's/^default:(lava_(flowing|source)) [0-9 ]+$/default:\1 255 100 0/g' $COLORSTXT_PATH -i
# Add transparency to glass nodes and xpanes:
# Add transparency to glass nodes:
sed -re 's/^default:(.*glass) ([0-9 ]+)$/default:\1 \2 64 16/g' $COLORSTXT_PATH -i
sed -re 's/^doors:(.*glass[^ ]*) ([0-9 ]+)$/doors:\1 \2 64 16/g' $COLORSTXT_PATH -i
sed -re 's/^xpanes:(.*(pane|bar)[^ ]*) ([0-9 ]+)$/xpanes:\1 \3 64 16/g' $COLORSTXT_PATH -i
# Delete some usually hidden nodes:
sed '/^doors:hidden /d' $COLORSTXT_PATH -i
sed '/^fireflies:firefly /d' $COLORSTXT_PATH -i
sed '/^butterflies:butterfly_/d' $COLORSTXT_PATH -i
# Fix xpanes color:
sed -re 's/^xpanes:((pane|bar)(_flat)?) [0-9 ]+$/xpanes:\1 194 194 227 64 16/g' $COLORSTXT_PATH -i
==INSTRUCTIONS==
1) Make sure avgcolors.py works (outputs the usage instructions when run)
1) Make sure avgcolors.py outputs the usage instructions
2) Add the dumpnodes mod to Minetest
3) Create a world and load dumpnodes & all mods you want to generate colors for
4) Execute /dumpnodes ingame
5) Run the script to generate colors.txt (make sure to adjust the PATH variables at the top)
5) Run the script to generate colors.txt (make sure to replace /path/to/... with the actual paths)

View File

@ -3,9 +3,8 @@
#ifndef CMAKE_CONFIG_H
#define CMAKE_CONFIG_H
#cmakedefine01 USE_POSTGRESQL
#cmakedefine01 USE_LEVELDB
#cmakedefine01 USE_REDIS
#define USE_LEVELDB @USE_LEVELDB@
#define USE_REDIS @USE_REDIS@
#define SHAREDIR "@SHAREDIR@"

View File

@ -1,441 +1,277 @@
# beds
beds:bed_bottom 130 3 3
beds:bed_top 185 162 163
beds:fancy_bed_bottom 136 49 28
beds:fancy_bed_top 179 153 148
beds:bed_top 178 116 116
beds:fancy_bed_bottom 135 23 14
beds:fancy_bed_top 172 112 103
# bones
bones:bones 117 117 117
# butterflies
# carts
carts:brakerail 138 121 102
carts:powerrail 160 145 102
carts:rail 146 128 108
bones:bones 86 86 86
# default
default:acacia_bush_leaves 109 133 87
default:acacia_bush_sapling 85 121 61
default:acacia_bush_stem 84 77 70
default:acacia_leaves 126 153 101
default:acacia_sapling 87 120 64
default:acacia_tree 195 119 97
default:acacia_wood 150 61 39
default:apple 161 34 19
default:aspen_leaves 72 105 29
default:aspen_sapling 85 123 45
default:aspen_tree 218 198 168
default:aspen_wood 210 199 170
default:blueberry_bush_leaves 63 99 22
default:blueberry_bush_leaves_with_berries 63 99 22
default:blueberry_bush_sapling 81 112 33
default:bookshelf 131 102 57
default:brick 123 99 95
default:bronzeblock 186 111 15
default:bush_leaves 35 55 29
default:bush_sapling 66 64 40
default:bush_stem 46 34 24
default:cactus 70 119 52
default:cave_ice 168 206 247
default:chest 149 115 69
default:chest_locked 149 115 69
default:chest_locked_open 149 115 69
default:chest_open 149 115 69
default:clay 183 183 183
default:acacia_leaves 108 147 67
default:acacia_sapling 87 116 61
default:acacia_tree 188 109 90
default:acacia_wood 146 60 37
default:apple 145 20 9
default:aspen_leaves 66 89 38
default:aspen_sapling 82 110 43
default:aspen_tree 218 197 166
default:aspen_wood 209 198 169
default:bookshelf 128 99 55
default:brick 117 71 69
default:bronzeblock 185 110 15
default:cactus 52 116 15
default:chest 140 108 65
default:chest_locked 140 108 65
default:clay 182 182 182
default:cloud 255 255 255
default:coalblock 58 58 58
default:cobble 89 86 84
default:copperblock 193 126 65
default:coral_brown 146 113 77
default:coral_cyan 235 230 215
default:coral_green 235 230 215
default:coral_orange 197 68 17
default:coral_pink 235 230 215
default:coral_skeleton 235 230 215
default:desert_cobble 110 67 50
default:coalblock 57 57 57
default:cobble 88 84 82
default:copperblock 192 126 63
default:desert_cobble 146 95 76
default:desert_sand 206 165 98
default:desert_sandstone 195 152 92
default:desert_sandstone_block 193 152 94
default:desert_sandstone_brick 191 151 95
default:desert_stone 130 79 61
default:desert_stone_block 131 80 61
default:desert_stonebrick 131 80 61
default:diamondblock 140 218 223
default:dirt 97 67 43
default:dirt_with_coniferous_litter 109 90 71
default:dirt_with_dry_grass 187 148 78
default:dirt_with_grass 64 111 26
default:dirt_with_grass_footsteps 64 111 26
default:dirt_with_rainforest_litter 76 39 10
default:dirt_with_snow 225 225 238
default:dry_dirt 178 136 90
default:dry_dirt_with_dry_grass 187 148 78
default:desert_stone 129 79 60
default:desert_stone_block 130 79 60
default:desert_stonebrick 129 79 60
default:diamondblock 135 217 223
default:dirt 95 64 39
default:dirt_with_dry_grass 187 148 77
default:dirt_with_grass 66 112 31
default:dirt_with_grass_footsteps 66 112 31
default:dirt_with_snow 223 224 236
default:dry_grass_1 208 172 87
default:dry_grass_2 210 174 87
default:dry_grass_3 210 174 87
default:dry_grass_4 211 175 88
default:dry_grass_5 214 178 92
default:dry_shrub 103 67 18
default:emergent_jungle_sapling 51 40 16
default:fence_acacia_wood 151 62 39
default:dry_grass_5 213 178 92
default:dry_shrub 101 66 17
default:fence_acacia_wood 147 60 38
default:fence_aspen_wood 210 199 170
default:fence_junglewood 57 39 14
default:fence_pine_wood 221 185 131
default:fence_rail_acacia_wood 150 61 39
default:fence_rail_aspen_wood 209 198 170
default:fence_rail_junglewood 56 39 14
default:fence_rail_pine_wood 221 184 130
default:fence_rail_wood 131 102 57
default:fence_wood 132 103 57
default:fern_1 85 118 51
default:fern_2 90 123 53
default:fern_3 91 125 54
default:furnace 101 98 96
default:furnace_active 101 98 96
default:glass 247 247 247 64 16
default:goldblock 231 203 35
default:grass_1 100 140 54
default:grass_2 98 139 55
default:grass_3 94 136 53
default:grass_4 89 133 48
default:grass_5 86 126 48
default:gravel 132 132 132
default:ice 168 206 247
default:junglegrass 67 110 28
default:jungleleaves 22 31 16
default:junglesapling 51 39 15
default:jungletree 121 97 62
default:junglewood 56 39 14
default:ladder_steel 132 132 132
default:ladder_wood 125 93 43
default:large_cactus_seedling 67 107 52
default:fence_junglewood 54 37 11
default:fence_pine_wood 221 184 129
default:fence_wood 129 100 55
default:furnace 100 96 94
default:furnace_active 100 96 94
default:glass 194 194 227 64 16
default:goldblock 230 201 29
default:grass_1 97 138 53
default:grass_2 95 136 54
default:grass_3 91 133 52
default:grass_4 86 130 45
default:grass_5 83 124 47
default:gravel 131 131 131
default:ice 167 206 247
default:junglegrass 63 105 25
default:jungleleaves 20 28 15
default:junglesapling 48 38 14
default:jungletree 105 76 41
default:junglewood 54 37 11
default:ladder_steel 126 126 126
default:ladder_wood 123 90 34
default:lava_flowing 255 100 0
default:lava_source 255 100 0
default:leaves 36 55 29
default:marram_grass_1 113 139 96
default:marram_grass_2 102 131 90
default:marram_grass_3 99 130 88
default:mese 222 222 0
default:mese_post_light 134 105 59
default:meselamp 213 215 143
default:mossycobble 88 91 73
default:obsidian 21 24 29
default:obsidian_block 23 25 30
default:obsidian_glass 20 23 27 64 16
default:obsidianbrick 23 25 29
default:papyrus 97 134 38
default:permafrost 71 66 61
default:permafrost_with_moss 108 150 51
default:permafrost_with_stones 71 66 61
default:pine_bush_needles 16 50 19
default:pine_bush_sapling 58 51 40
default:pine_bush_stem 73 62 53
default:pine_needles 16 50 19
default:pine_sapling 41 48 26
default:pine_tree 191 165 132
default:pine_wood 221 185 130
default:leaves 34 52 29
default:mese 220 220 0
default:meselamp 211 213 139
default:mossycobble 86 90 68
default:obsidian 19 21 24
default:obsidian_block 20 22 25
default:obsidian_glass 19 21 23 64 16
default:obsidianbrick 20 22 24
default:papyrus 94 132 33
default:pine_needles 13 36 21
default:pine_sapling 27 48 25
default:pine_tree 182 155 124
default:pine_wood 221 184 128
default:rail 143 123 90
default:river_water_flowing 39 66 106 128 224
default:river_water_source 39 66 106 128 224
default:sand 214 207 158
default:sand_with_kelp 214 207 158
default:sandstone 198 193 143
default:sandstone_block 195 191 142
default:sandstonebrick 194 190 141
default:sapling 67 63 41
default:sign_wall_steel 147 147 147
default:sign_wall_wood 148 103 66
default:sandstone 197 193 143
default:sandstone_block 195 190 141
default:sandstonebrick 193 189 140
default:sapling 65 59 40
default:sign_wall_steel 144 144 144
default:sign_wall_wood 145 101 64
default:silver_sand 193 191 179
default:silver_sandstone 195 192 181
default:silver_sandstone_block 192 190 180
default:silver_sandstone_brick 191 189 179
default:snow 225 225 238
default:snowblock 225 225 238
default:steelblock 195 195 195
default:snow 223 224 236
default:snowblock 223 224 236
default:steelblock 194 194 194
default:stone 97 94 93
default:stone_block 100 97 96
default:stone_block 99 96 95
default:stone_with_coal 97 94 93
default:stone_with_copper 97 94 93
default:stone_with_diamond 97 94 93
default:stone_with_gold 97 94 93
default:stone_with_iron 97 94 93
default:stone_with_mese 97 94 93
default:stone_with_tin 97 94 93
default:stonebrick 102 99 98
default:tinblock 150 150 150
default:torch 141 123 93
default:torch_ceiling 141 123 93
default:torch_wall 141 123 93
default:tree 179 145 99
default:stonebrick 99 96 95
default:torch 120 98 67
default:tree 164 131 88
default:water_flowing 39 66 106 128 224
default:water_source 39 66 106 128 224
default:wood 131 102 57
default:wood 128 99 55
# doors
doors:door_glass_a 245 245 245 64 16
doors:door_glass_b 245 245 245 64 16
doors:door_obsidian_glass_a 48 49 50 64 16
doors:door_obsidian_glass_b 48 49 50 64 16
doors:door_steel_a 203 203 203
doors:door_steel_b 203 203 203
doors:door_wood_a 89 68 37
doors:door_wood_b 89 68 37
doors:gate_acacia_wood_closed 150 61 39
doors:gate_acacia_wood_open 150 61 39
doors:gate_aspen_wood_closed 210 199 170
doors:gate_aspen_wood_open 210 199 170
doors:gate_junglewood_closed 56 39 14
doors:gate_junglewood_open 56 39 14
doors:gate_pine_wood_closed 221 185 130
doors:gate_pine_wood_open 221 185 130
doors:gate_wood_closed 131 102 57
doors:gate_wood_open 131 102 57
doors:trapdoor 130 100 51
doors:trapdoor_open 68 53 30
doors:trapdoor_steel 200 200 200
doors:trapdoor_steel_open 97 97 97
doors:door_glass_a 184 184 216 64 16
doors:door_glass_b 184 184 216 64 16
doors:door_obsidian_glass_a 27 28 29 64 16
doors:door_obsidian_glass_b 27 28 29 64 16
doors:door_steel_a 201 201 201
doors:door_steel_b 201 201 201
doors:door_wood_a 87 67 35
doors:door_wood_b 87 67 35
doors:gate_acacia_wood_closed 146 60 37
doors:gate_acacia_wood_open 146 60 37
doors:gate_aspen_wood_closed 209 198 169
doors:gate_aspen_wood_open 209 198 169
doors:gate_junglewood_closed 54 37 11
doors:gate_junglewood_open 54 37 11
doors:gate_pine_wood_closed 221 184 128
doors:gate_pine_wood_open 221 184 128
doors:gate_wood_closed 128 99 55
doors:gate_wood_open 128 99 55
doors:hidden 0 0 0
doors:trapdoor 128 99 50
doors:trapdoor_open 225 217 206
doors:trapdoor_steel 199 199 199
doors:trapdoor_steel_open 206 206 206
# farming
farming:cotton_1 89 117 39
farming:cotton_2 89 116 38
farming:cotton_3 99 121 41
farming:cotton_4 108 114 47
farming:cotton_5 116 105 53
farming:cotton_6 121 95 59
farming:cotton_7 94 70 37
farming:cotton_8 122 108 93
farming:desert_sand_soil 161 132 72
farming:desert_sand_soil_wet 120 99 53
farming:dry_soil 178 136 90
farming:dry_soil_wet 178 136 90
farming:seed_cotton 92 87 60
farming:seed_wheat 177 161 96
farming:soil 97 67 43
farming:soil_wet 97 67 43
farming:straw 212 184 68
farming:wheat_1 110 175 36
farming:wheat_2 136 177 53
farming:wheat_3 163 182 84
farming:wheat_4 170 188 95
farming:wheat_5 171 179 97
farming:wheat_6 173 177 87
farming:wheat_7 193 181 83
farming:wheat_8 187 162 40
farming:cotton_1 88 116 39
farming:cotton_2 87 116 38
farming:cotton_3 97 120 41
farming:cotton_4 106 113 47
farming:cotton_5 114 104 53
farming:cotton_6 119 94 59
farming:cotton_7 92 69 37
farming:cotton_8 110 91 61
farming:desert_sand_soil 159 131 70
farming:desert_sand_soil_wet 119 98 52
farming:seed_cotton 90 85 57
farming:seed_wheat 175 159 93
farming:soil 95 64 39
farming:soil_wet 95 64 39
farming:straw 211 182 67
farming:wheat_1 107 174 32
farming:wheat_2 133 175 50
farming:wheat_3 162 182 82
farming:wheat_4 169 187 93
farming:wheat_5 169 178 94
farming:wheat_6 172 176 85
farming:wheat_7 192 181 81
farming:wheat_8 186 161 35
# fire
fire:basic_flame 223 136 44
fire:permanent_flame 223 136 44
# fireflies
fireflies:firefly_bottle 191 194 202
fire:basic_flame 218 120 36
fire:permanent_flame 218 120 36
# flowers
flowers:chrysanthemum_green 118 152 44
flowers:dandelion_white 199 191 176
flowers:dandelion_yellow 212 167 31
flowers:geranium 77 91 168
flowers:mushroom_brown 109 84 78
flowers:mushroom_red 195 102 102
flowers:rose 130 68 33
flowers:tulip 156 101 44
flowers:tulip_black 78 120 72
flowers:viola 115 69 184
flowers:waterlily 107 160 68
flowers:waterlily_waving 107 160 68
flowers:dandelion_white 178 176 140
flowers:dandelion_yellow 178 148 24
flowers:geranium 72 87 155
flowers:mushroom_brown 106 79 72
flowers:mushroom_red 192 85 85
flowers:rose 118 48 24
flowers:tulip 130 99 36
flowers:viola 106 60 159
flowers:waterlily 102 158 61
# nyancat
nyancat:nyancat 198 111 167
nyancat:nyancat_rainbow 123 100 94
# stairs
stairs:slab_acacia_wood 150 61 39
stairs:slab_aspen_wood 210 199 170
stairs:slab_brick 123 99 95
stairs:slab_bronzeblock 186 111 15
stairs:slab_cobble 89 86 84
stairs:slab_copperblock 193 126 65
stairs:slab_desert_cobble 110 67 50
stairs:slab_desert_sandstone 195 152 92
stairs:slab_desert_sandstone_block 193 152 94
stairs:slab_desert_sandstone_brick 191 151 95
stairs:slab_desert_stone 130 79 61
stairs:slab_desert_stone_block 131 80 61
stairs:slab_desert_stonebrick 131 80 61
stairs:slab_glass 247 247 247
stairs:slab_goldblock 231 203 35
stairs:slab_ice 168 206 247
stairs:slab_junglewood 56 39 14
stairs:slab_mossycobble 88 91 73
stairs:slab_obsidian 21 24 29
stairs:slab_obsidian_block 23 25 30
stairs:slab_obsidian_glass 20 23 27
stairs:slab_obsidianbrick 23 25 29
stairs:slab_pine_wood 221 185 130
stairs:slab_sandstone 198 193 143
stairs:slab_sandstone_block 195 191 142
stairs:slab_sandstonebrick 194 190 141
stairs:slab_silver_sandstone 195 192 181
stairs:slab_silver_sandstone_block 192 190 180
stairs:slab_silver_sandstone_brick 191 189 179
stairs:slab_snowblock 225 225 238
stairs:slab_steelblock 195 195 195
stairs:slab_acacia_wood 146 60 37
stairs:slab_aspen_wood 209 198 169
stairs:slab_brick 117 71 69
stairs:slab_bronzeblock 185 110 15
stairs:slab_cobble 88 84 82
stairs:slab_copperblock 192 126 63
stairs:slab_desert_cobble 146 95 76
stairs:slab_desert_stone 129 79 60
stairs:slab_desert_stone_block 130 79 60
stairs:slab_desert_stonebrick 129 79 60
stairs:slab_goldblock 230 201 29
stairs:slab_junglewood 54 37 11
stairs:slab_mossycobble 86 90 68
stairs:slab_obsidian 19 21 24
stairs:slab_obsidian_block 20 22 25
stairs:slab_obsidianbrick 20 22 24
stairs:slab_pine_wood 221 184 128
stairs:slab_sandstone 197 193 143
stairs:slab_sandstone_block 195 190 141
stairs:slab_sandstonebrick 193 189 140
stairs:slab_steelblock 194 194 194
stairs:slab_stone 97 94 93
stairs:slab_stone_block 100 97 96
stairs:slab_stonebrick 102 99 98
stairs:slab_straw 212 184 68
stairs:slab_tinblock 150 150 150
stairs:slab_wood 131 102 57
stairs:stair_acacia_wood 150 61 39
stairs:stair_aspen_wood 210 199 170
stairs:stair_brick 123 99 95
stairs:stair_bronzeblock 186 111 15
stairs:stair_cobble 89 86 84
stairs:stair_copperblock 193 126 65
stairs:stair_desert_cobble 110 67 50
stairs:stair_desert_sandstone 195 152 92
stairs:stair_desert_sandstone_block 193 152 94
stairs:stair_desert_sandstone_brick 191 151 95
stairs:stair_desert_stone 130 79 61
stairs:stair_desert_stone_block 131 80 61
stairs:stair_desert_stonebrick 131 80 61
stairs:stair_glass 249 249 249
stairs:stair_goldblock 231 203 35
stairs:stair_ice 168 206 247
stairs:stair_inner_acacia_wood 150 61 39
stairs:stair_inner_aspen_wood 210 199 170
stairs:stair_inner_brick 123 99 95
stairs:stair_inner_bronzeblock 186 111 15
stairs:stair_inner_cobble 89 86 84
stairs:stair_inner_copperblock 193 126 65
stairs:stair_inner_desert_cobble 110 67 50
stairs:stair_inner_desert_sandstone 195 152 92
stairs:stair_inner_desert_sandstone_block 193 152 94
stairs:stair_inner_desert_sandstone_brick 191 151 95
stairs:stair_inner_desert_stone 130 79 61
stairs:stair_inner_desert_stone_block 131 80 61
stairs:stair_inner_desert_stonebrick 131 80 61
stairs:stair_inner_glass 250 250 250
stairs:stair_inner_goldblock 231 203 35
stairs:stair_inner_ice 168 206 247
stairs:stair_inner_junglewood 56 39 14
stairs:stair_inner_mossycobble 88 91 73
stairs:stair_inner_obsidian 21 24 29
stairs:stair_inner_obsidian_block 23 25 30
stairs:stair_inner_obsidian_glass 20 22 27
stairs:stair_inner_obsidianbrick 23 25 29
stairs:stair_inner_pine_wood 221 185 130
stairs:stair_inner_sandstone 198 193 143
stairs:stair_inner_sandstone_block 195 191 142
stairs:stair_inner_sandstonebrick 194 190 141
stairs:stair_inner_silver_sandstone 195 192 181
stairs:stair_inner_silver_sandstone_block 192 190 180
stairs:stair_inner_silver_sandstone_brick 191 189 179
stairs:stair_inner_snowblock 225 225 238
stairs:stair_inner_steelblock 195 195 195
stairs:stair_inner_stone 97 94 93
stairs:stair_inner_stone_block 100 97 96
stairs:stair_inner_stonebrick 102 99 98
stairs:stair_inner_straw 212 184 68
stairs:stair_inner_tinblock 150 150 150
stairs:stair_inner_wood 131 102 57
stairs:stair_junglewood 56 39 14
stairs:stair_mossycobble 88 91 73
stairs:stair_obsidian 21 24 29
stairs:stair_obsidian_block 23 25 30
stairs:stair_obsidian_glass 20 22 27
stairs:stair_obsidianbrick 23 25 29
stairs:stair_outer_acacia_wood 150 61 39
stairs:stair_outer_aspen_wood 210 199 170
stairs:stair_outer_brick 123 99 95
stairs:stair_outer_bronzeblock 186 111 15
stairs:stair_outer_cobble 89 86 84
stairs:stair_outer_copperblock 193 126 65
stairs:stair_outer_desert_cobble 110 67 50
stairs:stair_outer_desert_sandstone 195 152 92
stairs:stair_outer_desert_sandstone_block 193 152 94
stairs:stair_outer_desert_sandstone_brick 191 151 95
stairs:stair_outer_desert_stone 130 79 61
stairs:stair_outer_desert_stone_block 131 80 61
stairs:stair_outer_desert_stonebrick 131 80 61
stairs:stair_outer_glass 250 250 250
stairs:stair_outer_goldblock 231 203 35
stairs:stair_outer_ice 168 206 247
stairs:stair_outer_junglewood 56 39 14
stairs:stair_outer_mossycobble 88 91 73
stairs:stair_outer_obsidian 21 24 29
stairs:stair_outer_obsidian_block 23 25 30
stairs:stair_outer_obsidian_glass 20 22 27
stairs:stair_outer_obsidianbrick 23 25 29
stairs:stair_outer_pine_wood 221 185 130
stairs:stair_outer_sandstone 198 193 143
stairs:stair_outer_sandstone_block 195 191 142
stairs:stair_outer_sandstonebrick 194 190 141
stairs:stair_outer_silver_sandstone 195 192 181
stairs:stair_outer_silver_sandstone_block 192 190 180
stairs:stair_outer_silver_sandstone_brick 191 189 179
stairs:stair_outer_snowblock 225 225 238
stairs:stair_outer_steelblock 195 195 195
stairs:stair_outer_stone 97 94 93
stairs:stair_outer_stone_block 100 97 96
stairs:stair_outer_stonebrick 102 99 98
stairs:stair_outer_straw 212 184 68
stairs:stair_outer_tinblock 150 150 150
stairs:stair_outer_wood 131 102 57
stairs:stair_pine_wood 221 185 130
stairs:stair_sandstone 198 193 143
stairs:stair_sandstone_block 195 191 142
stairs:stair_sandstonebrick 194 190 141
stairs:stair_silver_sandstone 195 192 181
stairs:stair_silver_sandstone_block 192 190 180
stairs:stair_silver_sandstone_brick 191 189 179
stairs:stair_snowblock 225 225 238
stairs:stair_steelblock 195 195 195
stairs:slab_stone_block 99 96 95
stairs:slab_stonebrick 99 96 95
stairs:slab_straw 211 182 67
stairs:slab_wood 128 99 55
stairs:stair_acacia_wood 146 60 37
stairs:stair_aspen_wood 209 198 169
stairs:stair_brick 117 71 69
stairs:stair_bronzeblock 185 110 15
stairs:stair_cobble 88 84 82
stairs:stair_copperblock 192 126 63
stairs:stair_desert_cobble 146 95 76
stairs:stair_desert_stone 129 79 60
stairs:stair_desert_stone_block 130 79 60
stairs:stair_desert_stonebrick 129 79 60
stairs:stair_goldblock 230 201 29
stairs:stair_junglewood 54 37 11
stairs:stair_mossycobble 86 90 68
stairs:stair_obsidian 19 21 24
stairs:stair_obsidian_block 20 22 25
stairs:stair_obsidianbrick 20 22 24
stairs:stair_pine_wood 221 184 128
stairs:stair_sandstone 197 193 143
stairs:stair_sandstone_block 195 190 141
stairs:stair_sandstonebrick 193 189 140
stairs:stair_steelblock 194 194 194
stairs:stair_stone 97 94 93
stairs:stair_stone_block 100 97 96
stairs:stair_stonebrick 102 99 98
stairs:stair_straw 212 184 68
stairs:stair_tinblock 150 150 150
stairs:stair_wood 131 102 57
stairs:stair_stone_block 99 96 95
stairs:stair_stonebrick 99 96 95
stairs:stair_straw 211 182 67
stairs:stair_wood 128 99 55
# tnt
tnt:gunpowder 12 12 12
tnt:gunpowder_burning 156 143 7
tnt:tnt 196 0 0
tnt:tnt_burning 201 41 0
tnt:gunpowder 6 6 6
tnt:gunpowder_burning 112 103 2
tnt:tnt 181 0 0
tnt:tnt_burning 190 8 0
# vessels
vessels:drinking_glass 207 214 228
vessels:glass_bottle 189 192 204
vessels:shelf 131 102 57
vessels:steel_bottle 194 193 193
vessels:drinking_glass 206 214 228
vessels:glass_bottle 188 190 201
vessels:shelf 128 99 55
vessels:steel_bottle 193 192 191
# walls
walls:cobble 89 86 84
walls:desertcobble 110 67 50
walls:mossycobble 88 91 73
walls:cobble 88 84 82
walls:desertcobble 146 95 76
walls:mossycobble 86 90 68
# wool
wool:black 30 30 30
wool:blue 0 73 146
wool:brown 88 44 0
wool:cyan 0 132 140
wool:dark_green 33 103 0
wool:dark_grey 60 60 60
wool:green 93 218 28
wool:grey 133 133 133
wool:magenta 201 3 112
wool:orange 214 83 22
wool:pink 255 133 133
wool:red 170 18 18
wool:violet 93 5 169
wool:black 29 29 29
wool:blue 0 72 145
wool:brown 86 42 0
wool:cyan 0 130 139
wool:dark_green 32 102 0
wool:dark_grey 59 59 59
wool:green 92 216 28
wool:grey 131 131 131
wool:magenta 200 3 109
wool:orange 213 81 21
wool:pink 255 131 131
wool:red 168 17 17
wool:violet 92 2 169
wool:white 220 220 220
wool:yellow 254 226 16
wool:yellow 254 225 15
# xpanes
xpanes:bar 114 114 114 64 16
xpanes:bar_flat 114 114 114 64 16
xpanes:door_steel_bar_a 133 133 133 64 16
xpanes:door_steel_bar_b 133 133 133 64 16
xpanes:obsidian_pane 16 17 18 64 16
xpanes:obsidian_pane_flat 16 17 18 64 16
xpanes:pane 249 249 249 64 16
xpanes:pane_flat 249 249 249 64 16
xpanes:trapdoor_steel_bar 127 127 127 64 16
xpanes:trapdoor_steel_bar_open 77 77 77 64 16
xpanes:bar 194 194 227 64 16
xpanes:bar_flat 194 194 227 64 16
xpanes:pane 194 194 227 64 16
xpanes:pane_flat 194 194 227 64 16

View File

@ -9,7 +9,6 @@
#ifdef USE_CMAKE_CONFIG_H
#include "cmake_config.h"
#else
#define USE_POSTGRESQL 0
#define USE_LEVELDB 0
#define USE_REDIS 0

View File

@ -11,6 +11,7 @@ static inline int64_t stoi64(const std::string &s)
return t;
}
static inline std::string i64tos(int64_t i)
{
std::ostringstream os;
@ -18,7 +19,6 @@ static inline std::string i64tos(int64_t i)
return os.str();
}
DBLevelDB::DBLevelDB(const std::string &mapdir)
{
leveldb::Options options;
@ -28,9 +28,6 @@ DBLevelDB::DBLevelDB(const std::string &mapdir)
throw std::runtime_error(std::string("Failed to open Database: ") + status.ToString());
}
/* LevelDB is a dumb key-value store, so the only optimization we can do
* is to cache the block positions that exist in the db.
*/
loadPosCache();
}
@ -41,21 +38,9 @@ DBLevelDB::~DBLevelDB()
}
std::vector<BlockPos> DBLevelDB::getBlockPos(BlockPos min, BlockPos max)
std::vector<BlockPos> DBLevelDB::getBlockPos()
{
std::vector<BlockPos> res;
for (const auto &it : posCache) {
if (it.first < min.z || it.first >= max.z)
continue;
for (auto pos2 : it.second) {
if (pos2.first < min.x || pos2.first >= max.x)
continue;
if (pos2.second < min.y || pos2.second >= max.y)
continue;
res.emplace_back(pos2.first, pos2.second, it.first);
}
}
return res;
return posCache;
}
@ -64,51 +49,26 @@ void DBLevelDB::loadPosCache()
leveldb::Iterator * it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
int64_t posHash = stoi64(it->key().ToString());
BlockPos pos = decodeBlockPos(posHash);
posCache[pos.z].emplace_back(pos.x, pos.y);
posCache.push_back(decodeBlockPos(posHash));
}
delete it;
}
void DBLevelDB::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y)
void DBLevelDB::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
{
std::string datastr;
leveldb::Status status;
auto it = posCache.find(z);
if (it == posCache.cend())
return;
for (auto pos2 : it->second) {
if (pos2.first != x)
for (std::vector<BlockPos>::iterator it = posCache.begin(); it != posCache.end(); ++it) {
if (it->z != zPos) {
continue;
if (pos2.second < min_y || pos2.second >= max_y)
continue;
BlockPos pos(x, pos2.second, z);
status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(pos)), &datastr);
}
status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(*it)), &datastr);
if (status.ok()) {
blocks.emplace_back(
pos, ustring((unsigned char *) datastr.data(), datastr.size())
);
Block b(*it, ustring((const unsigned char *) datastr.data(), datastr.size()));
blocks[b.first.x].push_back(b);
}
}
}
void DBLevelDB::getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions)
{
std::string datastr;
leveldb::Status status;
for (auto pos : positions) {
status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(pos)), &datastr);
if (status.ok()) {
blocks.emplace_back(
pos, ustring((unsigned char *) datastr.data(), datastr.size())
);
}
}
}

21
db-leveldb.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef DB_LEVELDB_HEADER
#define DB_LEVELDB_HEADER
#include "db.h"
#include <leveldb/db.h>
class DBLevelDB : public DB {
public:
DBLevelDB(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos();
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
virtual ~DBLevelDB();
private:
void loadPosCache();
std::vector<BlockPos> posCache;
leveldb::DB *db;
};
#endif // DB_LEVELDB_HEADER

View File

@ -1,226 +0,0 @@
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <arpa/inet.h>
#include "db-postgresql.h"
#include "util.h"
#include "types.h"
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
DBPostgreSQL::DBPostgreSQL(const std::string &mapdir)
{
std::ifstream ifs((mapdir + "/world.mt").c_str());
if(!ifs.good())
throw std::runtime_error("Failed to read world.mt");
std::string connect_string = read_setting("pgsql_connection", ifs);
ifs.close();
db = PQconnectdb(connect_string.c_str());
if (PQstatus(db) != CONNECTION_OK) {
throw std::runtime_error(std::string(
"PostgreSQL database error: ") +
PQerrorMessage(db)
);
}
prepareStatement(
"get_block_pos",
"SELECT posX::int4, posY::int4, posZ::int4 FROM blocks WHERE"
" (posX BETWEEN $1::int4 AND $2::int4) AND"
" (posY BETWEEN $3::int4 AND $4::int4) AND"
" (posZ BETWEEN $5::int4 AND $6::int4)"
);
prepareStatement(
"get_blocks",
"SELECT posY::int4, data FROM blocks WHERE"
" posX = $1::int4 AND posZ = $2::int4"
" AND (posY BETWEEN $3::int4 AND $4::int4)"
);
prepareStatement(
"get_block_exact",
"SELECT data FROM blocks WHERE"
" posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"
);
checkResults(PQexec(db, "START TRANSACTION;"));
checkResults(PQexec(db, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;"));
}
DBPostgreSQL::~DBPostgreSQL()
{
try {
checkResults(PQexec(db, "COMMIT;"));
} catch (const std::exception& caught) {
std::cerr << "could not finalize: " << caught.what() << std::endl;
}
PQfinish(db);
}
std::vector<BlockPos> DBPostgreSQL::getBlockPos(BlockPos min, BlockPos max)
{
int32_t const x1 = htonl(min.x);
int32_t const x2 = htonl(max.x - 1);
int32_t const y1 = htonl(min.y);
int32_t const y2 = htonl(max.y - 1);
int32_t const z1 = htonl(min.z);
int32_t const z2 = htonl(max.z - 1);
const void *args[] = { &x1, &x2, &y1, &y2, &z1, &z2 };
const int argLen[] = { 4, 4, 4, 4, 4, 4 };
const int argFmt[] = { 1, 1, 1, 1, 1, 1 };
PGresult *results = execPrepared(
"get_block_pos", ARRLEN(args), args,
argLen, argFmt, false
);
int numrows = PQntuples(results);
std::vector<BlockPos> positions;
positions.reserve(numrows);
for (int row = 0; row < numrows; ++row)
positions.emplace_back(pg_to_blockpos(results, row, 0));
PQclear(results);
return positions;
}
void DBPostgreSQL::getBlocksOnXZ(BlockList &blocks, int16_t xPos, int16_t zPos,
int16_t min_y, int16_t max_y)
{
int32_t const x = htonl(xPos);
int32_t const z = htonl(zPos);
int32_t const y1 = htonl(min_y);
int32_t const y2 = htonl(max_y - 1);
const void *args[] = { &x, &z, &y1, &y2 };
const int argLen[] = { 4, 4, 4, 4 };
const int argFmt[] = { 1, 1, 1, 1 };
PGresult *results = execPrepared(
"get_blocks", ARRLEN(args), args,
argLen, argFmt, false
);
int numrows = PQntuples(results);
for (int row = 0; row < numrows; ++row) {
BlockPos position;
position.x = xPos;
position.y = pg_binary_to_int(results, row, 0);
position.z = zPos;
blocks.emplace_back(
position,
ustring(
reinterpret_cast<unsigned char*>(
PQgetvalue(results, row, 1)
),
PQgetlength(results, row, 1)
)
);
}
PQclear(results);
}
void DBPostgreSQL::getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions)
{
int32_t x, y, z;
const void *args[] = { &x, &y, &z };
const int argLen[] = { 4, 4, 4 };
const int argFmt[] = { 1, 1, 1 };
for (auto pos : positions) {
x = htonl(pos.x);
y = htonl(pos.y);
z = htonl(pos.z);
PGresult *results = execPrepared(
"get_block_exact", ARRLEN(args), args,
argLen, argFmt, false
);
if (PQntuples(results) > 0) {
blocks.emplace_back(
pos,
ustring(
reinterpret_cast<unsigned char*>(
PQgetvalue(results, 0, 0)
),
PQgetlength(results, 0, 0)
)
);
}
PQclear(results);
}
}
PGresult *DBPostgreSQL::checkResults(PGresult *res, bool clear)
{
ExecStatusType statusType = PQresultStatus(res);
switch (statusType) {
case PGRES_COMMAND_OK:
case PGRES_TUPLES_OK:
break;
case PGRES_FATAL_ERROR:
throw std::runtime_error(
std::string("PostgreSQL database error: ") +
PQresultErrorMessage(res)
);
default:
throw std::runtime_error(
"Unhandled PostgreSQL result code"
);
}
if (clear)
PQclear(res);
return res;
}
void DBPostgreSQL::prepareStatement(const std::string &name, const std::string &sql)
{
checkResults(PQprepare(db, name.c_str(), sql.c_str(), 0, NULL));
}
PGresult *DBPostgreSQL::execPrepared(
const char *stmtName, const int paramsNumber,
const void **params,
const int *paramsLengths, const int *paramsFormats,
bool clear
)
{
return checkResults(PQexecPrepared(db, stmtName, paramsNumber,
(const char* const*) params, paramsLengths, paramsFormats,
1 /* binary output */), clear
);
}
int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col)
{
int32_t* raw = reinterpret_cast<int32_t*>(PQgetvalue(res, row, col));
return ntohl(*raw);
}
BlockPos DBPostgreSQL::pg_to_blockpos(PGresult *res, int row, int col)
{
BlockPos result;
result.x = pg_binary_to_int(res, row, col);
result.y = pg_binary_to_int(res, row, col + 1);
result.z = pg_binary_to_int(res, row, col + 2);
return result;
}

View File

@ -28,32 +28,40 @@ static inline std::string i64tos(int64_t i)
return os.str();
}
std::string get_setting_default(std::string name, std::istream &is, const std::string def)
{
try {
return get_setting(name, is);
} catch(std::runtime_error e) {
return def;
}
}
DBRedis::DBRedis(const std::string &mapdir)
{
std::ifstream ifs((mapdir + "/world.mt").c_str());
if(!ifs.good())
throw std::runtime_error("Failed to read world.mt");
std::string tmp;
tmp = read_setting("redis_address", ifs);
ifs.seekg(0);
hash = read_setting("redis_hash", ifs);
ifs.seekg(0);
try {
tmp = get_setting("redis_address", ifs);
ifs.seekg(0);
hash = get_setting("redis_hash", ifs);
ifs.seekg(0);
} catch(std::runtime_error e) {
throw std::runtime_error("Set redis_address and redis_hash in world.mt to use the redis backend");
}
const char *addr = tmp.c_str();
int port = stoi64(read_setting_default("redis_port", ifs, "6379"));
ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
if(!ctx) {
int port = stoi64(get_setting_default("redis_port", ifs, "6379"));
ctx = redisConnect(addr, port);
if(!ctx)
throw std::runtime_error("Cannot allocate redis context");
} else if(ctx->err) {
else if(ctx->err) {
std::string err = std::string("Connection error: ") + ctx->errstr;
redisFree(ctx);
throw std::runtime_error(err);
}
/* Redis is just a key-value store, so the only optimization we can do
* is to cache the block positions that exist in the db.
*/
loadPosCache();
}
@ -64,25 +72,13 @@ DBRedis::~DBRedis()
}
std::vector<BlockPos> DBRedis::getBlockPos(BlockPos min, BlockPos max)
std::vector<BlockPos> DBRedis::getBlockPos()
{
std::vector<BlockPos> res;
for (const auto &it : posCache) {
if (it.first < min.z || it.first >= max.z)
continue;
for (auto pos2 : it.second) {
if (pos2.first < min.x || pos2.first >= max.x)
continue;
if (pos2.second < min.y || pos2.second >= max.y)
continue;
res.emplace_back(pos2.first, pos2.second, it.first);
}
}
return res;
return posCache;
}
const char *DBRedis::replyTypeStr(int type) {
std::string DBRedis::replyTypeStr(int type) {
switch(type) {
case REDIS_REPLY_STATUS:
return "REDIS_REPLY_STATUS";
@ -113,16 +109,14 @@ void DBRedis::loadPosCache()
for(size_t i = 0; i < reply->elements; i++) {
if(reply->element[i]->type != REDIS_REPLY_STRING)
REPLY_TYPE_ERR(reply->element[i], "HKEYS subreply");
BlockPos pos = decodeBlockPos(stoi64(reply->element[i]->str));
posCache[pos.z].emplace_back(pos.x, pos.y);
posCache.push_back(decodeBlockPos(stoi64(reply->element[i]->str)));
}
freeReplyObject(reply);
}
void DBRedis::HMGET(const std::vector<BlockPos> &positions,
std::function<void(std::size_t, ustring)> result)
void DBRedis::HMGET(const std::vector<BlockPos> &positions, std::vector<ustring> *result)
{
const char *argv[DB_REDIS_HMGET_NUMFIELDS + 2];
argv[0] = "HMGET";
@ -130,7 +124,6 @@ void DBRedis::HMGET(const std::vector<BlockPos> &positions,
std::vector<BlockPos>::const_iterator position = positions.begin();
std::size_t remaining = positions.size();
std::size_t abs_i = 0;
while (remaining > 0) {
const std::size_t batch_size =
(remaining > DB_REDIS_HMGET_NUMFIELDS) ? DB_REDIS_HMGET_NUMFIELDS : remaining;
@ -148,51 +141,50 @@ void DBRedis::HMGET(const std::vector<BlockPos> &positions,
if(!reply)
throw std::runtime_error("Redis command HMGET failed");
if (reply->type != REDIS_REPLY_ARRAY)
REPLY_TYPE_ERR(reply, "HMGET reply");
if (reply->type != REDIS_REPLY_ARRAY) {
freeReplyObject(reply);
REPLY_TYPE_ERR(reply, "HKEYS subreply");
}
if (reply->elements != batch_size) {
freeReplyObject(reply);
throw std::runtime_error("HMGET wrong number of elements");
}
for (std::size_t i = 0; i < reply->elements; ++i) {
for (std::size_t i = 0; i < batch_size; ++i) {
redisReply *subreply = reply->element[i];
if (subreply->type == REDIS_REPLY_NIL)
continue;
else if (subreply->type != REDIS_REPLY_STRING)
REPLY_TYPE_ERR(subreply, "HMGET subreply");
if (subreply->len == 0)
if(!subreply)
throw std::runtime_error("Redis command HMGET failed");
if (subreply->type != REDIS_REPLY_STRING) {
freeReplyObject(reply);
REPLY_TYPE_ERR(reply, "HKEYS subreply");
}
if (subreply->len == 0) {
freeReplyObject(reply);
throw std::runtime_error("HMGET empty string");
result(abs_i + i, ustring((const unsigned char *) subreply->str, subreply->len));
}
result->push_back(ustring((const unsigned char *) subreply->str, subreply->len));
}
freeReplyObject(reply);
abs_i += reply->elements;
remaining -= batch_size;
}
}
void DBRedis::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y)
void DBRedis::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
{
auto it = posCache.find(z);
if (it == posCache.cend())
return;
std::vector<BlockPos> positions;
for (auto pos2 : it->second) {
if (pos2.first == x && pos2.second >= min_y && pos2.second < max_y)
positions.emplace_back(x, pos2.second, z);
std::vector<BlockPos> z_positions;
for (std::vector<BlockPos>::const_iterator it = posCache.begin(); it != posCache.end(); ++it) {
if (it->z != zPos) {
continue;
}
z_positions.push_back(*it);
}
std::vector<ustring> z_blocks;
HMGET(z_positions, &z_blocks);
getBlocksByPos(blocks, positions);
}
void DBRedis::getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions)
{
auto result = [&] (std::size_t i, ustring data) {
blocks.emplace_back(positions[i], std::move(data));
};
HMGET(positions, result);
std::vector<ustring>::const_iterator z_block = z_blocks.begin();
for (std::vector<BlockPos>::const_iterator pos = z_positions.begin();
pos != z_positions.end();
++pos, ++z_block) {
blocks[pos->x].push_back(Block(*pos, *z_block));
}
}

25
db-redis.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef DB_REDIS_HEADER
#define DB_REDIS_HEADER
#include "db.h"
#include <hiredis.h>
class DBRedis : public DB {
public:
DBRedis(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos();
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
virtual ~DBRedis();
private:
static std::string replyTypeStr(int type);
void loadPosCache();
void HMGET(const std::vector<BlockPos> &positions, std::vector<ustring> *result);
std::vector<BlockPos> posCache;
redisContext *ctx;
std::string hash;
};
#endif // DB_REDIS_HEADER

View File

@ -1,8 +1,6 @@
#include <stdexcept>
#include <unistd.h> // for usleep
#include <iostream>
#include <algorithm>
#include <time.h>
#include "db-sqlite3.h"
#include "types.h"
@ -13,6 +11,7 @@
}
#define SQLOK(f) SQLRES(f, SQLITE_OK)
DBSQLite3::DBSQLite3(const std::string &mapdir)
{
int result;
@ -25,17 +24,9 @@ DBSQLite3::DBSQLite3(const std::string &mapdir)
"SELECT pos, data FROM blocks WHERE pos BETWEEN ? AND ?",
-1, &stmt_get_blocks_z, NULL))
SQLOK(prepare_v2(db,
"SELECT data FROM blocks WHERE pos = ?",
-1, &stmt_get_block_exact, NULL))
SQLOK(prepare_v2(db,
"SELECT pos FROM blocks",
-1, &stmt_get_block_pos, NULL))
SQLOK(prepare_v2(db,
"SELECT pos FROM blocks WHERE pos BETWEEN ? AND ?",
-1, &stmt_get_block_pos_z, NULL))
}
@ -43,154 +34,59 @@ DBSQLite3::~DBSQLite3()
{
sqlite3_finalize(stmt_get_blocks_z);
sqlite3_finalize(stmt_get_block_pos);
sqlite3_finalize(stmt_get_block_pos_z);
sqlite3_finalize(stmt_get_block_exact);
if (sqlite3_close(db) != SQLITE_OK) {
std::cerr << "Error closing SQLite database." << std::endl;
};
}
inline void DBSQLite3::getPosRange(int64_t &min, int64_t &max, int16_t zPos,
int16_t zPos2) const
{
/* The range of block positions is [-2048, 2047], which turns into [0, 4095]
* when casted to unsigned. This didn't actually help me understand the
* numbers below, but I wanted to write it down.
*/
// Magic numbers!
min = encodeBlockPos(BlockPos(0, -2048, zPos));
max = encodeBlockPos(BlockPos(0, 2048, zPos2)) - 1;
}
std::vector<BlockPos> DBSQLite3::getBlockPos(BlockPos min, BlockPos max)
std::vector<BlockPos> DBSQLite3::getBlockPos()
{
int result;
sqlite3_stmt *stmt;
if(min.z <= -2048 && max.z >= 2048) {
stmt = stmt_get_block_pos;
} else {
stmt = stmt_get_block_pos_z;
int64_t minPos, maxPos;
if (min.z < -2048)
min.z = -2048;
if (max.z > 2048)
max.z = 2048;
getPosRange(minPos, maxPos, min.z, max.z - 1);
SQLOK(bind_int64(stmt, 1, minPos))
SQLOK(bind_int64(stmt, 2, maxPos))
}
std::vector<BlockPos> positions;
while ((result = sqlite3_step(stmt)) != SQLITE_DONE) {
if (result == SQLITE_BUSY) { // Wait some time and try again
while ((result = sqlite3_step(stmt_get_block_pos)) != SQLITE_DONE) {
if (result == SQLITE_ROW) {
int64_t posHash = sqlite3_column_int64(stmt_get_block_pos, 0);
positions.push_back(decodeBlockPos(posHash));
} else if (result == SQLITE_BUSY) { // Wait some time and try again
usleep(10000);
} else if (result != SQLITE_ROW) {
} else {
throw std::runtime_error(sqlite3_errmsg(db));
}
int64_t posHash = sqlite3_column_int64(stmt, 0);
BlockPos pos = decodeBlockPos(posHash);
if(pos.x >= min.x && pos.x < max.x && pos.y >= min.y && pos.y < max.y)
positions.emplace_back(pos);
}
SQLOK(reset(stmt));
SQLOK(reset(stmt_get_block_pos));
return positions;
}
void DBSQLite3::loadBlockCache(int16_t zPos)
void DBSQLite3::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos)
{
int result;
blockCache.clear();
int64_t minPos, maxPos;
getPosRange(minPos, maxPos, zPos, zPos);
// Magic numbers!
int64_t minPos = encodeBlockPos(BlockPos(0, -2048, zPos));
int64_t maxPos = encodeBlockPos(BlockPos(0, 2048, zPos)) - 1;
SQLOK(bind_int64(stmt_get_blocks_z, 1, minPos));
SQLOK(bind_int64(stmt_get_blocks_z, 2, maxPos));
while ((result = sqlite3_step(stmt_get_blocks_z)) != SQLITE_DONE) {
if (result == SQLITE_BUSY) { // Wait some time and try again
usleep(10000);
} else if (result != SQLITE_ROW) {
throw std::runtime_error(sqlite3_errmsg(db));
}
int64_t posHash = sqlite3_column_int64(stmt_get_blocks_z, 0);
BlockPos pos = decodeBlockPos(posHash);
const unsigned char *data = reinterpret_cast<const unsigned char *>(
sqlite3_column_blob(stmt_get_blocks_z, 1));
size_t size = sqlite3_column_bytes(stmt_get_blocks_z, 1);
blockCache[pos.x].emplace_back(pos, ustring(data, size));
}
SQLOK(reset(stmt_get_blocks_z))
}
void DBSQLite3::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y)
{
/* Cache the blocks on the given Z coordinate between calls, this only
* works due to order in which the TileGenerator asks for blocks. */
if (z != blockCachedZ) {
loadBlockCache(z);
blockCachedZ = z;
}
auto it = blockCache.find(x);
if (it == blockCache.end())
return;
if (it->second.empty()) {
/* We have swapped this list before, this is not supposed to happen
* because it's bad for performance. But rather than silently breaking
* do the right thing and load the blocks again. */
#ifndef NDEBUG
std::cout << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl;
#endif
loadBlockCache(z);
}
// Swap lists to avoid copying contents
blocks.clear();
std::swap(blocks, it->second);
for (auto it = blocks.begin(); it != blocks.end(); ) {
if (it->first.y < min_y || it->first.y >= max_y)
it = blocks.erase(it);
else
it++;
}
}
void DBSQLite3::getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions)
{
int result;
for (auto pos : positions) {
int64_t dbPos = encodeBlockPos(pos);
SQLOK(bind_int64(stmt_get_block_exact, 1, dbPos));
while ((result = sqlite3_step(stmt_get_block_exact)) == SQLITE_BUSY) {
usleep(10000); // Wait some time and try again
}
if (result == SQLITE_DONE) {
// no data
} else if (result != SQLITE_ROW) {
throw std::runtime_error(sqlite3_errmsg(db));
} else {
if (result == SQLITE_ROW) {
int64_t posHash = sqlite3_column_int64(stmt_get_blocks_z, 0);
const unsigned char *data = reinterpret_cast<const unsigned char *>(
sqlite3_column_blob(stmt_get_block_exact, 0));
size_t size = sqlite3_column_bytes(stmt_get_block_exact, 0);
blocks.emplace_back(pos, ustring(data, size));
sqlite3_column_blob(stmt_get_blocks_z, 1));
size_t size = sqlite3_column_bytes(stmt_get_blocks_z, 1);
Block b(decodeBlockPos(posHash), ustring(data, size));
blocks[b.first.x].push_back(b);
} else if (result == SQLITE_BUSY) { // Wait some time and try again
usleep(10000);
} else {
throw std::runtime_error(sqlite3_errmsg(db));
}
SQLOK(reset(stmt_get_block_exact))
}
SQLOK(reset(stmt_get_blocks_z));
}
#undef SQLRES
#undef SQLOK

20
db-sqlite3.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef _DB_SQLITE3_H
#define _DB_SQLITE3_H
#include "db.h"
#include <sqlite3.h>
class DBSQLite3 : public DB {
public:
DBSQLite3(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos();
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos);
virtual ~DBSQLite3();
private:
sqlite3 *db;
sqlite3_stmt *stmt_get_block_pos;
sqlite3_stmt *stmt_get_blocks_z;
};
#endif // _DB_SQLITE3_H

View File

@ -5,33 +5,39 @@
#include <map>
#include <list>
#include <vector>
#include <string>
#include <utility>
#include "types.h"
struct BlockPos {
class BlockPos {
public:
int16_t x;
int16_t y;
int16_t z;
BlockPos() : x(0), y(0), z(0) {}
BlockPos(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {}
// Implements the inverse ordering so that (2,2,2) < (1,1,1)
bool operator < (const BlockPos &p) const
{
if (z > p.z)
if (z > p.z) {
return true;
if (z < p.z)
}
if (z < p.z) {
return false;
if (y > p.y)
}
if (y > p.y) {
return true;
if (y < p.y)
}
if (y < p.y) {
return false;
if (x > p.x)
}
if (x > p.x) {
return true;
if (x < p.x)
}
if (x < p.x) {
return false;
}
return false;
}
};
@ -43,31 +49,13 @@ typedef std::list<Block> BlockList;
class DB {
protected:
// Helpers that implement the hashed positions used by most backends
inline int64_t encodeBlockPos(const BlockPos pos) const;
inline BlockPos decodeBlockPos(int64_t hash) const;
public:
/* Return all block positions inside the range given by min and max,
* so that min.x <= x < max.x, ...
*/
virtual std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) = 0;
/* Read all blocks in column given by x and z
* and inside the given Y range (min_y <= y < max_y) into list
*/
virtual void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y) = 0;
/* Read blocks at given positions into list
*/
virtual void getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions) = 0;
/* Can this database efficiently do range queries?
* (for large data sets, more efficient that brute force)
*/
virtual bool preferRangeQueries() const = 0;
virtual ~DB() {}
virtual std::vector<BlockPos> getBlockPos() = 0;
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) = 0;
virtual ~DB() {};
};

View File

@ -1,28 +0,0 @@
#ifndef BLOCKDECODER_H
#define BLOCKDECODER_H
#include <unordered_map>
#include "types.h"
class BlockDecoder {
public:
BlockDecoder();
void reset();
void decode(const ustring &data);
bool isEmpty() const;
std::string getNode(u8 x, u8 y, u8 z) const; // returns "" for air, ignore and invalid nodes
u8 getParam1(u8 x, u8 y, u8 z) const;
private:
typedef std::unordered_map<int, std::string> NameMap;
NameMap m_nameMap;
int m_blockAirId;
int m_blockIgnoreId;
u8 m_version, m_contentWidth;
ustring m_mapData;
};
#endif // BLOCKDECODER_H

View File

@ -1,30 +0,0 @@
#ifndef PLAYERATTRIBUTES_H_D7THWFVV
#define PLAYERATTRIBUTES_H_D7THWFVV
#include <list>
#include <string>
struct Player
{
std::string name;
double x, y, z;
};
class PlayerAttributes
{
public:
typedef std::list<Player> Players;
PlayerAttributes(const std::string &worldDir);
Players::iterator begin();
Players::iterator end();
private:
void readFiles(const std::string &playersPath);
void readSqlite(const std::string &db_name);
Players m_players;
};
#endif /* end of include guard: PLAYERATTRIBUTES_H_D7THWFVV */

View File

@ -1,31 +0,0 @@
#ifndef DB_LEVELDB_HEADER
#define DB_LEVELDB_HEADER
#include "db.h"
#include <unordered_map>
#include <utility>
#include <leveldb/db.h>
class DBLevelDB : public DB {
public:
DBLevelDB(const std::string &mapdir);
std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y) override;
void getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions) override;
~DBLevelDB() override;
bool preferRangeQueries() const override { return false; }
private:
using pos2d = std::pair<int16_t, int16_t>;
void loadPosCache();
// indexed by Z, contains all (x,y) position pairs
std::unordered_map<int16_t, std::vector<pos2d>> posCache;
leveldb::DB *db;
};
#endif // DB_LEVELDB_HEADER

View File

@ -1,35 +0,0 @@
#ifndef _DB_POSTGRESQL_H
#define _DB_POSTGRESQL_H
#include "db.h"
#include <libpq-fe.h>
class DBPostgreSQL : public DB {
public:
DBPostgreSQL(const std::string &mapdir);
std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y) override;
void getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions) override;
~DBPostgreSQL() override;
bool preferRangeQueries() const override { return true; }
protected:
PGresult *checkResults(PGresult *res, bool clear = true);
void prepareStatement(const std::string &name, const std::string &sql);
PGresult *execPrepared(
const char *stmtName, const int paramsNumber,
const void **params,
const int *paramsLengths = NULL, const int *paramsFormats = NULL,
bool clear = true
);
int pg_binary_to_int(PGresult *res, int row, int col);
BlockPos pg_to_blockpos(PGresult *res, int row, int col);
private:
PGconn *db;
};
#endif // _DB_POSTGRESQL_H

View File

@ -1,37 +0,0 @@
#ifndef DB_REDIS_HEADER
#define DB_REDIS_HEADER
#include "db.h"
#include <unordered_map>
#include <utility>
#include <functional>
#include <hiredis/hiredis.h>
class DBRedis : public DB {
public:
DBRedis(const std::string &mapdir);
std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y) override;
void getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions) override;
~DBRedis() override;
bool preferRangeQueries() const override { return false; }
private:
using pos2d = std::pair<int16_t, int16_t>;
static const char *replyTypeStr(int type);
void loadPosCache();
void HMGET(const std::vector<BlockPos> &positions,
std::function<void(std::size_t, ustring)> result);
// indexed by Z, contains all (x,y) position pairs
std::unordered_map<int16_t, std::vector<pos2d>> posCache;
redisContext *ctx;
std::string hash;
};
#endif // DB_REDIS_HEADER

View File

@ -1,36 +0,0 @@
#ifndef _DB_SQLITE3_H
#define _DB_SQLITE3_H
#include "db.h"
#include <unordered_map>
#include <sqlite3.h>
class DBSQLite3 : public DB {
public:
DBSQLite3(const std::string &mapdir);
std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y) override;
void getBlocksByPos(BlockList &blocks,
const std::vector<BlockPos> &positions) override;
~DBSQLite3() override;
bool preferRangeQueries() const override { return false; }
private:
inline void getPosRange(int64_t &min, int64_t &max, int16_t zPos,
int16_t zPos2) const;
void loadBlockCache(int16_t zPos);
sqlite3 *db;
sqlite3_stmt *stmt_get_block_pos;
sqlite3_stmt *stmt_get_block_pos_z;
sqlite3_stmt *stmt_get_blocks_z;
sqlite3_stmt *stmt_get_block_exact;
int16_t blockCachedZ = -10000;
std::unordered_map<int16_t, BlockList> blockCache; // indexed by X
};
#endif // _DB_SQLITE3_H

View File

@ -1,18 +0,0 @@
#ifndef UTIL_H
#define UTIL_H
#include <string>
#include <fstream>
std::string read_setting(const std::string &name, std::istream &is);
inline std::string read_setting_default(const std::string &name, std::istream &is, const std::string &def)
{
try {
return read_setting(name, is);
} catch(const std::runtime_error &e) {
return def;
}
}
#endif // UTIL_H

View File

@ -1,68 +1,47 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <getopt.h>
#include <fstream>
#include <iostream>
#include <utility>
#include <map>
#include <string>
#include <sstream>
#include <stdexcept>
#include "cmake_config.h"
#include "TileGenerator.h"
static void usage()
void usage()
{
const std::pair<const char*, const char*> options[] = {
{"-i/--input", "<world_path>"},
{"-o/--output", "<output_image.png>"},
{"--bgcolor", "<color>"},
{"--scalecolor", "<color>"},
{"--playercolor", "<color>"},
{"--origincolor", "<color>"},
{"--drawscale", ""},
{"--drawplayers", ""},
{"--draworigin", ""},
{"--drawalpha", ""},
{"--noshading", ""},
{"--noemptyimage", ""},
{"--min-y", "<y>"},
{"--max-y", "<y>"},
{"--backend", "<backend>"},
{"--geometry", "x:y+w+h"},
{"--extent", ""},
{"--zoom", "<zoomlevel>"},
{"--colors", "<colors.txt>"},
{"--scales", "[t][b][l][r]"},
{"--exhaustive", "never|y|full|auto"},
};
const char *top_text =
"minetestmapper -i <world_path> -o <output_image.png> [options]\n"
"Generate an overview image of a Minetest map.\n"
"\n"
"Options:\n";
const char *bottom_text =
"\n"
"Color format: hexadecimal '#RRGGBB', e.g. '#FF0000' = red\n";
printf("%s", top_text);
for (const auto &p : options)
printf(" %-18s%s\n", p.first, p.second);
printf("%s", bottom_text);
auto backends = TileGenerator::getSupportedBackends();
printf("Supported backends: ");
for (auto s : backends)
printf("%s ", s.c_str());
printf("\n");
const char *usage_text = "minetestmapper [options]\n"
" -i/--input <world_path>\n"
" -o/--output <output_image.png>\n"
" --bgcolor <color>\n"
" --scalecolor <color>\n"
" --playercolor <color>\n"
" --origincolor <color>\n"
" --drawscale\n"
" --drawplayers\n"
" --draworigin\n"
" --drawalpha\n"
" --noshading\n"
" --min-y <y>\n"
" --max-y <y>\n"
" --backend <backend>\n"
" --geometry x:y+w+h\n"
" --zoom <zoomlevel>\n"
" --colors <colors.txt>\n"
" --scales [t][b][l][r]\n"
"Color format: '#000000'\n";
std::cout << usage_text;
}
static bool file_exists(const std::string &path)
bool file_exists(const std::string &path)
{
std::ifstream ifs(path.c_str());
return ifs.is_open();
}
static std::string search_colors(const std::string &worldpath)
std::string search_colors(const std::string &worldpath)
{
if(file_exists(worldpath + "/colors.txt"))
return worldpath + "/colors.txt";
@ -76,8 +55,7 @@ static std::string search_colors(const std::string &worldpath)
}
#endif
constexpr bool sharedir_valid = !(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0');
if(sharedir_valid && file_exists(SHAREDIR "/colors.txt"))
if(!(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0') && file_exists(SHAREDIR "/colors.txt"))
return SHAREDIR "/colors.txt";
std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl;
@ -86,7 +64,7 @@ static std::string search_colors(const std::string &worldpath)
int main(int argc, char *argv[])
{
const static struct option long_options[] =
static struct option long_options[] =
{
{"help", no_argument, 0, 'h'},
{"input", required_argument, 0, 'i'},
@ -102,14 +80,11 @@ int main(int argc, char *argv[])
{"noshading", no_argument, 0, 'H'},
{"backend", required_argument, 0, 'd'},
{"geometry", required_argument, 0, 'g'},
{"extent", no_argument, 0, 'E'},
{"min-y", required_argument, 0, 'a'},
{"max-y", required_argument, 0, 'c'},
{"zoom", required_argument, 0, 'z'},
{"colors", required_argument, 0, 'C'},
{"scales", required_argument, 0, 'f'},
{"noemptyimage", no_argument, 0, 'n'},
{"exhaustive", required_argument, 0, 'j'},
{0, 0, 0, 0}
};
@ -118,13 +93,17 @@ int main(int argc, char *argv[])
std::string colors = "";
TileGenerator generator;
bool onlyPrintExtent = false;
int option_index = 0;
int c = 0;
while (1) {
int option_index;
int c = getopt_long(argc, argv, "hi:o:", long_options, &option_index);
if (c == -1)
break; // done
c = getopt_long(argc, argv, "hi:o:", long_options, &option_index);
if (c == -1) {
if (input.empty() || output.empty()) {
usage();
return 0;
}
break;
}
switch (c) {
case 'h':
usage();
@ -160,9 +139,6 @@ int main(int argc, char *argv[])
case 'e':
generator.setDrawAlpha(true);
break;
case 'E':
onlyPrintExtent = true;
break;
case 'H':
generator.setShading(false);
break;
@ -170,21 +146,24 @@ int main(int argc, char *argv[])
generator.setBackend(optarg);
break;
case 'a': {
std::istringstream iss(optarg);
std::istringstream iss;
iss.str(optarg);
int miny;
iss >> miny;
generator.setMinY(miny);
}
break;
case 'c': {
std::istringstream iss(optarg);
std::istringstream iss;
iss.str(optarg);
int maxy;
iss >> maxy;
generator.setMaxY(maxy);
}
break;
case 'g': {
std::istringstream geometry(optarg);
std::istringstream geometry;
geometry.str(optarg);
int x, y, w, h;
char c;
geometry >> x >> c >> y >> w >> h;
@ -209,7 +188,8 @@ int main(int argc, char *argv[])
}
break;
case 'z': {
std::istringstream iss(optarg);
std::istringstream iss;
iss.str(optarg);
int zoom;
iss >> zoom;
generator.setZoom(zoom);
@ -218,45 +198,16 @@ int main(int argc, char *argv[])
case 'C':
colors = optarg;
break;
case 'n':
generator.setDontWriteEmpty(true);
break;
case 'j': {
int mode;
if (!strcmp(optarg, "never"))
mode = EXH_NEVER;
else if (!strcmp(optarg, "y"))
mode = EXH_Y;
else if (!strcmp(optarg, "full"))
mode = EXH_FULL;
else
mode = EXH_AUTO;
generator.setExhaustiveSearch(mode);
}
break;
default:
exit(1);
}
}
if (input.empty() || (!onlyPrintExtent && output.empty())) {
usage();
return 0;
}
if(colors == "")
colors = search_colors(input);
try {
if (onlyPrintExtent) {
generator.printGeometry(input);
return 0;
}
if(colors == "")
colors = search_colors(input);
generator.parseColorsFile(colors);
generator.generate(input, output);
} catch(std::runtime_error &e) {
} catch(std::runtime_error e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}

View File

@ -1,110 +0,0 @@
.TH MINETESTMAPPER 6
.SH NAME
minetestmapper \- generate an overview image of a Minetest map
.SH SYNOPSIS
.B minetestmapper
\fB\-i\fR \fIworld_path\fR
\fB\-o\fR \fIoutput_image\fR
.PP
See additional optional parameters below.
.SH DESCRIPTION
.B minetestmapper
generates an overview image of a minetest map. This is a port of
the original minetestmapper.py to C++, that is both faster and
provides more functionality than the deprecated Python script.
.SH MANDATORY PARAMETERS
.TP
.BR \-i " " \fIworld_path\fR
Input world path.
.TP
.BR \-o " " \fIoutput_image\fR
Path to output image. (only PNG supported currently)
.SH OPTIONAL PARAMETERS
.TP
.BR \-\-bgcolor " " \fIcolor\fR
Background color of image, e.g. "--bgcolor #ffffff"
.TP
.BR \-\-scalecolor " " \fIcolor\fR
Color of scale, e.g. "--scalecolor #000000"
.TP
.BR \-\-playercolor " " \fIcolor\fR
Color of player indicators, e.g. "--playercolor #ff0000"
.TP
.BR \-\-origincolor " " \fIcolor\fR
Color of origin indicator, e.g. "--origincolor #ff0000"
.TP
.BR \-\-drawscale
Draw tick marks
.TP
.BR \-\-drawplayers
Draw player indicators
.TP
.BR \-\-draworigin
Draw origin indicator
.TP
.BR \-\-drawalpha
Allow nodes to be drawn with transparency
.TP
.BR \-\-noshading
Don't draw shading on nodes
.TP
.BR \-\-noemptyimage
Don't output anything when the image would be empty.
.TP
.BR \-\-min-y " " \fInumber\fR
Don't draw nodes below this y value, e.g. "--min-y -25"
.TP
.BR \-\-max-y " " \fInumber\fR
Don't draw nodes above this y value, e.g. "--max-y 75"
.TP
.BR \-\-backend " " \fIbackend\fR
Use specific map backend; supported: \fIsqlite3\fP, \fIleveldb\fP, \fIredis\fP, \fIpostgresql\fP, e.g. "--backend leveldb"
.TP
.BR \-\-geometry " " \fIgeometry\fR
Limit area to specific geometry (\fIx:y+w+h\fP where x and y specify the lower left corner), e.g. "--geometry -800:-800+1600+1600"
.TP
.BR \-\-extent " " \fIextent\fR
Don't render the image, just print the extent of the map that would be generated, in the same format as the geometry above.
.TP
.BR \-\-zoom " " \fIfactor\fR
Zoom the image by using more than one pixel per node, e.g. "--zoom 4"
.TP
.BR \-\-colors " " \fIpath\fR
Forcefully set path to colors.txt file (it's autodetected otherwise), e.g. "--colors ../minetest/mycolors.txt"
.TP
.BR \-\-scales " " \fIedges\fR
Draw scales on specified image edges (letters \fIt b l r\fP meaning top, bottom, left and right), e.g. "--scales tbr"
.TP
.BR \-\-exhaustive " \fImode\fR
Select if database should be traversed exhaustively or using range queries, available: \fInever\fP, \fIy\fP, \fIfull\fP, \fIauto\fP
Defaults to \fIauto\fP. You shouldn't need to change this, but doing so can improve rendering times on large maps.
For these optimizations to work it is important that you set
.B min-y
and
.B max-y
when you don't care about the world below e.g. -60 and above 1000 nodes.
.SH MORE INFORMATION
Website: https://github.com/minetest/minetestmapper
.SH MAN PAGE AUTHOR
Daniel Moerner

View File

@ -1,45 +1,56 @@
#include <stdexcept>
#include <sstream>
#include "util.h"
static inline std::string trim(const std::string &s)
inline std::string trim(const std::string &s)
{
auto isspace = [] (char c) -> bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; };
size_t front = 0;
while(s[front] == ' ' ||
s[front] == '\t' ||
s[front] == '\r' ||
s[front] == '\n'
)
++front;
size_t front = 0;
while(isspace(s[front]))
++front;
size_t back = s.size() - 1;
while(back > front && isspace(s[back]))
--back;
size_t back = s.size();
while(back > front &&
(s[back-1] == ' ' ||
s[back-1] == '\t' ||
s[back-1] == '\r' ||
s[back-1] == '\n'
)
)
--back;
return s.substr(front, back - front + 1);
return s.substr(front, back - front);
}
std::string read_setting(const std::string &name, std::istream &is)
#define EOFCHECK() \
if(is.eof()) \
throw std::runtime_error(((std::string) "Setting '") + name + "' not found");
std::string get_setting(std::string name, std::istream &is)
{
char linebuf[512];
while (is.good()) {
is.getline(linebuf, sizeof(linebuf));
char c;
char s[256];
std::string nm, value;
for(char *p = linebuf; *p; p++) {
if(*p != '#')
continue;
*p = '\0'; // Cut off at the first #
break;
}
std::string line(linebuf);
auto pos = line.find('=');
if (pos == std::string::npos)
continue;
auto key = trim(line.substr(0, pos));
if (key != name)
continue;
return trim(line.substr(pos+1));
}
std::ostringstream oss;
oss << "Setting '" << name << "' not found";
throw std::runtime_error(oss.str());
next:
while((c = is.get()) == ' ' || c == '\t' || c == '\r' || c == '\n')
;
EOFCHECK();
if(c == '#') // Ignore comments
is.ignore(0xffff, '\n');
EOFCHECK();
s[0] = c; // The current char belongs to the name too
is.get(&s[1], 255, '=');
is.ignore(1); // Jump over the =
EOFCHECK();
nm = trim(std::string(s));
is.get(s, 256, '\n');
value = trim(std::string(s));
if(name == nm)
return value;
else
goto next;
}
#undef EOFCHECK

10
util.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _UTIL_H
#define _UTIL_H
#include <string>
#include <stdexcept>
#include <fstream>
std::string get_setting(std::string name, std::istream &is);
#endif // _UTIL_H

View File

@ -1,73 +0,0 @@
#!/bin/bash -e
#######
# this expects an env similar to what minetest's buildbot uses
# extradll_path will typically contain libgcc, libstdc++ and libpng
toolchain_file=
toolchain_file64=
libgd_dir=
libgd_dir64=
zlib_dir=
zlib_dir64=
sqlite_dir=
sqlite_dir64=
leveldb_dir=
leveldb_dir64=
extradll_path=
extradll_path64=
#######
[ -f ./CMakeLists.txt ] || exit 1
if [ "$1" == "32" ]; then
:
elif [ "$1" == "64" ]; then
toolchain_file=$toolchain_file64
libgd_dir=$libgd_dir64
zlib_dir=$zlib_dir64
sqlite_dir=$sqlite_dir64
leveldb_dir=$leveldb_dir64
extradll_path=$extradll_path64
else
echo "Usage: $0 <32 / 64>"
exit 1
fi
cmake . \
-DCMAKE_INSTALL_PREFIX=/tmp \
-DCMAKE_TOOLCHAIN_FILE=$toolchain_file \
-DCMAKE_EXE_LINKER_FLAGS="-s" \
\
-DENABLE_LEVELDB=1 \
\
-DLIBGD_INCLUDE_DIR=$libgd_dir/include \
-DLIBGD_LIBRARY=$libgd_dir/lib/libgd.dll.a \
\
-DZLIB_INCLUDE_DIR=$zlib_dir/include \
-DZLIB_LIBRARY=$zlib_dir/lib/libz.dll.a \
\
-DSQLITE3_INCLUDE_DIR=$sqlite_dir/include \
-DSQLITE3_LIBRARY=$sqlite_dir/lib/libsqlite3.dll.a \
\
-DLEVELDB_INCLUDE_DIR=$leveldb_dir/include \
-DLEVELDB_LIBRARY=$leveldb_dir/lib/libleveldb.dll.a
make -j4
mkdir pack
cp -p \
AUTHORS colors.txt COPYING README.rst \
minetestmapper.exe \
$libgd_dir/bin/libgd-3.dll \
$zlib_dir/bin/zlib1.dll \
$sqlite_dir/bin/libsqlite3-0.dll \
$leveldb_dir/bin/libleveldb.dll \
$extradll_path/*.dll \
pack/
zipfile=minetestmapper-win$1.zip
(cd pack; zip -9r ../$zipfile *)
make clean
rm -r pack CMakeCache.txt
echo "Done."

View File

@ -1,8 +1,17 @@
#!/bin/bash -e
CXX=g++-6
[ $CC == "clang" ] && CXX=clang-3.8
export CXX
mkdir -p travisbuild
cd travisbuild
cmake .. \
-DENABLE_LEVELDB=1
cmake \
-DENABLE_LEVELDB=1 \
-DLEVELDB_LIBRARY=../libleveldb/lib/libleveldb.so \
-DLEVELDB_INCLUDE_DIR=../libleveldb/include \
..
make -j2