Compare commits

...

43 Commits

Author SHA1 Message Date
sfan5 dd1904a667 Run CI test also with ASan 2024-04-20 21:38:15 +02:00
sfan5 40a5e16e21 Save a copy during zlib decompression 2024-04-20 21:38:05 +02:00
wsor4035 7e4caacb9e
Upgrade CI workflow packages and Ubuntu version (#101) 2024-02-17 20:54:03 +01:00
sfan5 e14f27f412 Add cautionary note about sat_mul() 2023-08-08 14:18:58 +02:00
Saria 7af222dd9d Fix `dumpnodes` crash on deprecated tile field name 2023-04-08 16:32:29 +02:00
Martin Petricek c81cda24d3
Fix bad slicing interval for negative --min-y / --max-y values 2023-01-29 16:22:41 +01:00
sfan5 8a7333ef49 Fix package install in CI
shoutout to github for shipping broken OS images
2023-01-29 14:44:03 +01:00
sfan5 7fb3b9edd6 Fix Postgres linking on older CMake
(see 998e4820c9)
2022-06-19 14:14:09 +02:00
sfan5 18f0615002 Fix --drawplayers 2022-02-21 18:42:54 +01:00
sfan5 d75266eae1 Update colors.txt
with all nodes as of Minetest Game 5.5.0
2022-02-21 18:08:54 +01:00
sfan5 31b0d09a19 Warn if only unknown nodes seen
suggested by @Calinou
2022-02-09 23:09:32 +01:00
sfan5 e4bf375ac7 General code cleanups/maintenance 2022-02-09 23:09:32 +01:00
sfan5 b491dd375a Add --dumpblock flag for advanced use
This is not only useful for debugging minetestmapper itself but
also makes it a standalone tool for extracting data you want to work on
from a Minetest map.
2022-02-09 21:52:28 +01:00
sfan5 2e353312b5 Inherit custom exceptions from std::exception 2022-02-08 23:43:20 +01:00
sfan5 8e9805c3ff Fix overflowing multiplication leading to apparent hang
closes #88
2022-02-08 23:43:20 +01:00
sfan5 9b26d9495c
Update dependency list in README
fixes #87
2021-12-27 13:19:12 +01:00
sfan5 0198897306
Mention colors.txt generation in README
closes #86
2021-09-11 15:40:34 +02:00
sfan5 2f3a548881 Make MinGW build script work again 2021-09-03 22:02:35 +02:00
sfan5 ccd5d14962 Add progress bar during map generation
closes #82
2021-09-03 21:06:23 +02:00
sfan5 f471554294 Fix ZstdDecompressor error check 2021-09-02 11:45:17 +02:00
sfan5 b0ca3d7066 Add support for map block version 29 2021-09-01 23:57:37 +02:00
sfan5 5c435f6459 Add simple functional test to CI 2021-08-27 20:53:16 +02:00
sfan5 8b563f409e Modernize CMake build script
also includes this fix: a24899bf2d
2021-08-27 17:30:42 +02:00
sfan5 f26070ef4f Switch from Travis-CI to Github Actions 2021-08-27 16:56:24 +02:00
sfan5 fd4c5dd232 Fix CMake version warning 2021-03-29 15:05:02 +02:00
sfan5 fa5c63cfc8 Rewrite colors.txt generation script for more functions and better usability 2020-12-24 16:43:02 +01:00
srinivas32 e88fcf0dd8
Added ppc64le architecture to travis-ci (#81) 2020-11-26 21:36:44 +01:00
clavinet 6bb818ac2f
Update Linux instructions in README (#78) 2020-06-01 13:35:41 +02:00
sfan5 8e83ce6464 Some more code modernization
also a few small performance improvements
2020-05-08 22:16:13 +02:00
sfan5 2979dc5b6b Fix compatibility of MapBlock decoding
also properly drop support for version < 22, which hasn't worked in years
2020-05-06 22:32:27 +02:00
sfan5 92f6b051a5 Fall back to sqlite3 if no backend set in world.mt
fixes #76
2020-04-23 17:23:05 +02:00
sfan5 2ae790c0b7 Improve --help output 2020-03-28 14:02:27 +01:00
sfan5 539bdbd30c Fix another bug in the Redis backend
introduced in 7ff2288
2020-03-28 00:56:11 +01:00
sfan5 48bf44c72d Fix minY/maxY calculation (closes #66) 2020-03-28 00:40:38 +01:00
sfan5 cb8341aeab Implement --exhaustive y mode as another database access optimization
This one works best when you have a wide area with low height (e.g. 10000x200x10000)
2020-03-28 00:14:47 +01:00
sfan5 7ff2288627 Optimize database access further by allowing "brute-force" queries instead of listing available blocks
Also adds a heuristic that will enable this behaviour automatically.
2020-03-27 23:38:18 +01:00
sfan5 5b264fd443 Rewrite DB class to allow backends to fully optimize block fetches 2020-03-27 20:30:13 +01:00
sfan5 ecc2b31f78 Rewrite config file parser
I noticed it didn't work correctly in some cases...
2020-03-27 19:33:42 +01:00
sfan5 04b9dffb11 Properly support -DENABLE_REDIS=TRUE even if library is not found 2020-03-27 16:27:55 +01:00
sfan5 84c4fc40f8 Fix bug introduced in 9096f70 2020-03-27 12:45:31 +01:00
sfan5 a160dc051c Sort out include path mess in CMakeLists 2020-03-27 11:19:25 +01:00
sfan5 9096f70188 C++11 code modernization 2020-03-26 23:14:47 +01:00
sfan5 1d678ffa82
Fix typo in manpage
closes #74
2019-10-17 15:10:04 +02:00
45 changed files with 1873 additions and 1041 deletions

63
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: build
# build on c/cpp changes or workflow changes
on:
push:
paths:
- '**.[ch]'
- '**.cpp'
- '**/CMakeLists.txt'
- 'util/ci/**'
- '.github/workflows/**.yml'
pull_request:
paths:
- '**.[ch]'
- '**.cpp'
- '**/CMakeLists.txt'
- 'util/ci/**'
- '.github/workflows/**.yml'
jobs:
gcc:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
source util/ci/script.sh
install_linux_deps
- name: Build
run: |
source util/ci/script.sh
run_build
env:
CC: gcc
CXX: g++
- name: Test
run: |
source util/ci/script.sh
do_functional_test
clang:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
source util/ci/script.sh
install_linux_deps
- name: Build
run: |
source util/ci/script.sh
run_build
env:
CC: clang
CXX: clang++
- name: Test
run: |
source util/ci/script.sh
do_functional_test

10
.gitignore vendored
View File

@ -1,10 +1,14 @@
colors.txt *~
minetestmapper minetestmapper
minetestmapper.exe minetestmapper.exe
colors.txt
CMakeCache.txt CMakeCache.txt
CMakeFiles/ CMakeFiles/
CPack* CPack*.cmake
_CPack_Packages/
install_manifest.txt
Makefile Makefile
cmake_install.cmake cmake_install.cmake
cmake_config.h cmake_config.h
*~

View File

@ -1,11 +0,0 @@
language: cpp
compiler:
- gcc
- clang
dist: bionic
before_install: sudo apt-get install -y cmake libgd-dev libsqlite3-dev libleveldb-dev
script: ./util/travis/script.sh
notifications:
email: false
matrix:
fast_finish: true

View File

@ -1,4 +1,3 @@
#include <stdint.h>
#include <string> #include <string>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
@ -11,20 +10,19 @@ static inline uint16_t readU16(const unsigned char *data)
return data[0] << 8 | data[1]; return data[0] << 8 | data[1];
} }
static int readBlockContent(const unsigned char *mapData, u8 version, unsigned int datapos) static inline uint16_t readBlockContent(const unsigned char *mapData,
u8 contentWidth, unsigned int datapos)
{ {
if (version >= 24) { if (contentWidth == 2) {
size_t index = datapos << 1; size_t index = datapos << 1;
return (mapData[index] << 8) | mapData[index + 1]; return (mapData[index] << 8) | mapData[index + 1];
} else if (version >= 20) { } else {
if (mapData[datapos] <= 0x80) u8 param = mapData[datapos];
return mapData[datapos]; if (param <= 0x7f)
return param;
else else
return (int(mapData[datapos]) << 4) | (int(mapData[datapos + 0x2000]) >> 4); return (param << 4) | (mapData[datapos + 0x2000] >> 4);
} }
std::ostringstream oss;
oss << "Unsupported map version " << version;
throw std::runtime_error(oss.str());
} }
BlockDecoder::BlockDecoder() BlockDecoder::BlockDecoder()
@ -39,7 +37,8 @@ void BlockDecoder::reset()
m_nameMap.clear(); m_nameMap.clear();
m_version = 0; m_version = 0;
m_mapData = ustring(); m_contentWidth = 0;
m_mapData.clear();
} }
void BlockDecoder::decode(const ustring &datastr) void BlockDecoder::decode(const ustring &datastr)
@ -49,26 +48,77 @@ void BlockDecoder::decode(const ustring &datastr)
// TODO: bounds checks // TODO: bounds checks
uint8_t version = data[0]; 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; m_version = version;
ustring datastr2;
if (version >= 29) {
// decompress whole block at once
m_zstd_decompressor.setData(data, length, 1);
datastr2 = m_zstd_decompressor.decompress();
data = datastr2.c_str();
length = datastr2.size();
}
size_t dataOffset = 0; size_t dataOffset = 0;
if (version >= 27) if (version >= 29)
dataOffset = 6; dataOffset = 7;
else if (version >= 22) else if (version >= 27)
dataOffset = 4; dataOffset = 4;
else else
dataOffset = 2; dataOffset = 2;
auto decode_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;
}
};
if (version >= 29)
decode_mapping();
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;
if (version >= 29) {
m_mapData.resize((contentWidth + paramsWidth) * 4096);
m_mapData.assign(data + dataOffset, m_mapData.size());
return; // we have read everything we need and can return early
}
// version < 29
ZlibDecompressor decompressor(data, length); ZlibDecompressor decompressor(data, length);
decompressor.setSeekPos(dataOffset); decompressor.setSeekPos(dataOffset);
m_mapData = decompressor.decompress(); m_mapData = decompressor.decompress();
decompressor.decompress(); // unused metadata decompressor.decompress(); // unused metadata
dataOffset = decompressor.seekPos(); dataOffset = decompressor.seekPos();
// Skip unused data // Skip unused node timers
if (version <= 21)
dataOffset += 2;
if (version == 23) if (version == 23)
dataOffset += 1; dataOffset += 1;
if (version == 24) { if (version == 24) {
@ -92,33 +142,7 @@ void BlockDecoder::decode(const ustring &datastr)
dataOffset += 4; // Skip timestamp dataOffset += 4; // Skip timestamp
// Read mapping // Read mapping
if (version >= 22) { decode_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 bool BlockDecoder::isEmpty() const
@ -127,16 +151,18 @@ bool BlockDecoder::isEmpty() const
return m_nameMap.empty(); return m_nameMap.empty();
} }
std::string BlockDecoder::getNode(u8 x, u8 y, u8 z) const const static std::string empty;
const std::string &BlockDecoder::getNode(u8 x, u8 y, u8 z) const
{ {
unsigned int position = x + (y << 4) + (z << 8); unsigned int position = x + (y << 4) + (z << 8);
int content = readBlockContent(m_mapData.c_str(), m_version, position); uint16_t content = readBlockContent(m_mapData.c_str(), m_contentWidth, position);
if (content == m_blockAirId || content == m_blockIgnoreId) if (content == m_blockAirId || content == m_blockIgnoreId)
return ""; return empty;
NameMap::const_iterator it = m_nameMap.find(content); NameMap::const_iterator it = m_nameMap.find(content);
if (it == m_nameMap.end()) { if (it == m_nameMap.end()) {
std::cerr << "Skipping node with invalid ID." << std::endl; std::cerr << "Skipping node with invalid ID." << std::endl;
return ""; return empty;
} }
return it->second; return it->second;
} }

View File

@ -1,11 +1,9 @@
project(minetestmapper CXX) cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0003 NEW)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
set(VERSION_MAJOR 1) project(minetestmapper
set(VERSION_MINOR 0) VERSION 1.0
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}") LANGUAGES CXX
)
# Stuff & Paths # Stuff & Paths
@ -13,18 +11,18 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif() endif()
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -DNDEBUG") set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g2 -Wall") set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(WIN32) if(WIN32)
set(SHAREDIR ".") set(SHAREDIR ".")
set(BINDIR ".") set(BINDIR ".")
set(DOCDIR ".") set(DOCDIR ".")
else() else()
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/minetest") # reuse Minetest share dir set(SHAREDIR "share/minetest") # reuse Minetest share dir
set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin") set(BINDIR "bin")
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}") set(DOCDIR "share/doc/${PROJECT_NAME}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man") set(MANDIR "share/man")
endif() endif()
set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into") set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into")
@ -45,6 +43,7 @@ if(NOT CUSTOM_DOCDIR STREQUAL "")
message(STATUS "Using DOCDIR=${DOCDIR}") message(STATUS "Using DOCDIR=${DOCDIR}")
endif() endif()
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
# Libraries: gd # Libraries: gd
@ -58,21 +57,16 @@ endif(NOT LIBGD_LIBRARY OR NOT LIBGD_INCLUDE_DIR)
# Libraries: zlib # Libraries: zlib
find_library(ZLIB_LIBRARY z) find_package(ZLIB REQUIRED)
find_path(ZLIB_INCLUDE_DIR zlib.h)
message (STATUS "zlib library: ${ZLIB_LIBRARY}")
message (STATUS "zlib headers: ${ZLIB_INCLUDE_DIR}")
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) # Libraries: zstd
include(FindPackageHandleStandardArgs)
find_package(Zstd REQUIRED)
# Libraries: sqlite3 # Libraries: sqlite3
find_library(SQLITE3_LIBRARY sqlite3) find_library(SQLITE3_LIBRARY sqlite3)
find_path(SQLITE3_INCLUDE_DIR zlib.h) find_path(SQLITE3_INCLUDE_DIR sqlite3.h)
message (STATUS "sqlite3 library: ${SQLITE3_LIBRARY}") message (STATUS "sqlite3 library: ${SQLITE3_LIBRARY}")
message (STATUS "sqlite3 headers: ${SQLITE3_INCLUDE_DIR}") message (STATUS "sqlite3 headers: ${SQLITE3_INCLUDE_DIR}")
if(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR) if(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)
@ -82,77 +76,72 @@ endif(NOT SQLITE3_LIBRARY OR NOT SQLITE3_INCLUDE_DIR)
# Libraries: postgresql # Libraries: postgresql
option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE) option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
set(USE_POSTGRESQL 0) set(USE_POSTGRESQL FALSE)
if(ENABLE_POSTGRESQL) if(ENABLE_POSTGRESQL)
find_program(POSTGRESQL_CONFIG_EXECUTABLE pg_config DOC "pg_config") if(CMAKE_VERSION VERSION_LESS "3.20")
find_library(POSTGRESQL_LIBRARY pq) find_package(PostgreSQL QUIET)
if(POSTGRESQL_CONFIG_EXECUTABLE) # Before CMake 3.20 FindPostgreSQL.cmake always looked for server includes
execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} --includedir-server # but we don't need them, so continue anyway if only those are missing.
OUTPUT_VARIABLE POSTGRESQL_SERVER_INCLUDE_DIRS if(PostgreSQL_INCLUDE_DIR AND PostgreSQL_LIBRARY)
OUTPUT_STRIP_TRAILING_WHITESPACE) set(PostgreSQL_FOUND TRUE)
execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR})
OUTPUT_VARIABLE POSTGRESQL_CLIENT_INCLUDE_DIRS set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY})
OUTPUT_STRIP_TRAILING_WHITESPACE) endif()
# This variable is case sensitive for the cmake PostgreSQL module else()
set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${POSTGRESQL_SERVER_INCLUDE_DIRS} ${POSTGRESQL_CLIENT_INCLUDE_DIRS}) find_package(PostgreSQL)
endif() endif()
find_package("PostgreSQL") if(PostgreSQL_FOUND)
set(USE_POSTGRESQL TRUE)
if(POSTGRESQL_FOUND)
set(USE_POSTGRESQL 1)
message(STATUS "PostgreSQL backend enabled") message(STATUS "PostgreSQL backend enabled")
# This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR # This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR
message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIR}") message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIRS}")
include_directories(${PostgreSQL_INCLUDE_DIR}) include_directories(${PostgreSQL_INCLUDE_DIRS})
set(POSTGRESQL_LIBRARY ${PostgreSQL_LIBRARIES})
else() else()
message(STATUS "PostgreSQL not found.") message(STATUS "PostgreSQL not found!")
set(POSTGRESQL_LIBRARY "") set(PostgreSQL_LIBRARIES "")
endif() endif()
endif(ENABLE_POSTGRESQL) endif(ENABLE_POSTGRESQL)
# Libraries: leveldb # Libraries: leveldb
set(USE_LEVELDB 0) OPTION(ENABLE_LEVELDB "Enable LevelDB backend" TRUE)
set(USE_LEVELDB FALSE)
OPTION(ENABLE_LEVELDB "Enable LevelDB backend")
if(ENABLE_LEVELDB) if(ENABLE_LEVELDB)
find_library(LEVELDB_LIBRARY leveldb) find_library(LEVELDB_LIBRARY leveldb)
find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb) find_path(LEVELDB_INCLUDE_DIR leveldb/db.h)
message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}") message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}")
message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}") message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}")
if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
set(USE_LEVELDB 1) set(USE_LEVELDB TRUE)
message(STATUS "LevelDB backend enabled") message(STATUS "LevelDB backend enabled")
include_directories(${LEVELDB_INCLUDE_DIR}) include_directories(${LEVELDB_INCLUDE_DIR})
else(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) else()
set(USE_LEVELDB 0)
message(STATUS "LevelDB not found!") message(STATUS "LevelDB not found!")
endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) set(LEVELDB_LIBRARY "")
endif()
endif(ENABLE_LEVELDB) endif(ENABLE_LEVELDB)
# Libraries: redis # Libraries: redis
set(USE_REDIS 0) OPTION(ENABLE_REDIS "Enable redis backend" TRUE)
set(USE_REDIS FALSE)
OPTION(ENABLE_REDIS "Enable redis backend")
if(ENABLE_REDIS) if(ENABLE_REDIS)
find_library(REDIS_LIBRARY hiredis) find_library(REDIS_LIBRARY hiredis)
find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis) find_path(REDIS_INCLUDE_DIR hiredis/hiredis.h)
message (STATUS "redis library: ${REDIS_LIBRARY}") message (STATUS "redis library: ${REDIS_LIBRARY}")
message (STATUS "redis headers: ${REDIS_INCLUDE_DIR}") message (STATUS "redis headers: ${REDIS_INCLUDE_DIR}")
if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
set(USE_REDIS 1) set(USE_REDIS TRUE)
message(STATUS "redis backend enabled") message(STATUS "redis backend enabled")
include_directories(${REDIS_INCLUDE_DIR}) include_directories(${REDIS_INCLUDE_DIR})
else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) else()
set(USE_REDIS 0)
message(STATUS "redis not found!") message(STATUS "redis not found!")
endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) set(REDIS_LIBRARY "")
endif()
endif(ENABLE_REDIS) endif(ENABLE_REDIS)
# Compiling & Linking # Compiling & Linking
@ -164,50 +153,49 @@ include_directories(
${SQLITE3_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR}
${LIBGD_INCLUDE_DIR} ${LIBGD_INCLUDE_DIR}
${ZLIB_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}
${ZSTD_INCLUDE_DIR}
) )
configure_file( configure_file(
"${PROJECT_SOURCE_DIR}/include/cmake_config.h.in" "${PROJECT_SOURCE_DIR}/include/cmake_config.h.in"
"${PROJECT_BINARY_DIR}/cmake_config.h" "${PROJECT_BINARY_DIR}/cmake_config.h"
) )
add_definitions ( -DUSE_CMAKE_CONFIG_H ) add_definitions(-DUSE_CMAKE_CONFIG_H)
set(mapper_SRCS if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
set(CMAKE_CXX_FLAGS_DEBUG "-Og -g2")
add_compile_options(-Wall -pipe)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_definitions(-DNDEBUG)
endif()
add_executable(minetestmapper
BlockDecoder.cpp BlockDecoder.cpp
PixelAttributes.cpp PixelAttributes.cpp
PlayerAttributes.cpp PlayerAttributes.cpp
TileGenerator.cpp TileGenerator.cpp
ZlibDecompressor.cpp ZlibDecompressor.cpp
ZstdDecompressor.cpp
Image.cpp Image.cpp
mapper.cpp mapper.cpp
util.cpp util.cpp
db-sqlite3.cpp db-sqlite3.cpp
) $<$<BOOL:${USE_POSTGRESQL}>:db-postgresql.cpp>
$<$<BOOL:${USE_LEVELDB}>:db-leveldb.cpp>
if(USE_POSTGRESQL) $<$<BOOL:${USE_REDIS}>:db-redis.cpp>
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)
if(USE_REDIS)
set(mapper_SRCS ${mapper_SRCS} db-redis.cpp)
endif(USE_REDIS)
add_executable(minetestmapper
${mapper_SRCS}
) )
target_link_libraries( target_link_libraries(
minetestmapper minetestmapper
${SQLITE3_LIBRARY} ${SQLITE3_LIBRARY}
${POSTGRESQL_LIBRARY} ${PostgreSQL_LIBRARIES}
${LEVELDB_LIBRARY} ${LEVELDB_LIBRARY}
${REDIS_LIBRARY} ${REDIS_LIBRARY}
${LIBGD_LIBRARY} ${LIBGD_LIBRARY}
${ZLIB_LIBRARY} ${ZLIB_LIBRARY}
${ZSTD_LIBRARY}
) )
# Installing & Packaging # Installing & Packaging
@ -222,16 +210,14 @@ if(UNIX)
endif() endif()
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Overview mapper for Minetest") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Overview mapper for Minetest")
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
set(CPACK_PACKAGE_VENDOR "celeron55") set(CPACK_PACKAGE_VENDOR "celeron55")
set(CPACK_PACKAGE_CONTACT "Perttu Ahola <celeron55@gmail.com>") set(CPACK_PACKAGE_CONTACT "Perttu Ahola <celeron55@gmail.com>")
if(WIN32) if(WIN32)
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32") set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-win32")
set(CPACK_GENERATOR ZIP) set(CPACK_GENERATOR ZIP)
else() else()
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-linux") set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-linux")
set(CPACK_GENERATOR TGZ) set(CPACK_GENERATOR TGZ)
set(CPACK_SOURCE_GENERATOR TGZ) set(CPACK_SOURCE_GENERATOR TGZ)
endif() endif()

View File

@ -17,7 +17,7 @@
// ARGB but with inverted alpha // ARGB but with inverted alpha
static inline int color2int(Color c) static inline int color2int(const Color &c)
{ {
u8 a = (255 - c.a) * gdAlphaMax / 255; u8 a = (255 - c.a) * gdAlphaMax / 255;
return (a << 24) | (c.r << 16) | (c.g << 8) | c.b; return (a << 24) | (c.r << 16) | (c.g << 8) | c.b;
@ -26,15 +26,15 @@ static inline int color2int(Color c)
static inline Color int2color(int c) static inline Color int2color(int c)
{ {
Color c2; Color c2;
u8 a;
c2.b = c & 0xff; c2.b = c & 0xff;
c2.g = (c >> 8) & 0xff; c2.g = (c >> 8) & 0xff;
c2.r = (c >> 16) & 0xff; c2.r = (c >> 16) & 0xff;
a = (c >> 24) & 0xff; u8 a = (c >> 24) & 0xff;
c2.a = 255 - (a*255 / gdAlphaMax); c2.a = 255 - (a*255 / gdAlphaMax);
return c2; return c2;
} }
#ifndef NDEBUG
static inline void check_bounds(int x, int y, int width, int height) static inline void check_bounds(int x, int y, int width, int height)
{ {
if(x < 0 || x >= width) { if(x < 0 || x >= width) {
@ -50,11 +50,13 @@ static inline void check_bounds(int x, int y, int width, int height)
throw std::out_of_range(oss.str()); throw std::out_of_range(oss.str());
} }
} }
#endif
Image::Image(int width, int height) : Image::Image(int width, int height) :
m_width(width), m_height(height), m_image(NULL) m_width(width), m_height(height), m_image(nullptr)
{ {
SIZECHECK(0, 0);
m_image = gdImageCreateTrueColor(m_width, m_height); m_image = gdImageCreateTrueColor(m_width, m_height);
} }

View File

@ -1,23 +1,12 @@
/*
* =====================================================================
* Version: 1.0
* Created: 25.08.2012 10:55:27
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#include <cstdlib>
#include <cstring> #include <cstring>
#include "PixelAttributes.h"
using namespace std; #include "PixelAttributes.h"
PixelAttributes::PixelAttributes(): PixelAttributes::PixelAttributes():
m_width(0) m_width(0)
{ {
for (size_t i = 0; i < LineCount; ++i) { for (size_t i = 0; i < LineCount; ++i) {
m_pixelAttributes[i] = 0; m_pixelAttributes[i] = nullptr;
} }
} }
@ -47,9 +36,9 @@ void PixelAttributes::scroll()
void PixelAttributes::freeAttributes() void PixelAttributes::freeAttributes()
{ {
for (size_t i = 0; i < LineCount; ++i) { for (size_t i = 0; i < LineCount; ++i) {
if (m_pixelAttributes[i] != 0) { if (m_pixelAttributes[i] != nullptr) {
delete[] m_pixelAttributes[i]; delete[] m_pixelAttributes[i];
m_pixelAttributes[i] = 0; m_pixelAttributes[i] = nullptr;
} }
} }
} }

View File

@ -9,11 +9,9 @@
#include "PlayerAttributes.h" #include "PlayerAttributes.h"
#include "util.h" #include "util.h"
using namespace std;
PlayerAttributes::PlayerAttributes(const std::string &worldDir) PlayerAttributes::PlayerAttributes(const std::string &worldDir)
{ {
std::ifstream ifs((worldDir + "world.mt").c_str()); std::ifstream ifs(worldDir + "world.mt");
if (!ifs.good()) if (!ifs.good())
throw std::runtime_error("Failed to read world.mt"); throw std::runtime_error("Failed to read world.mt");
std::string backend = read_setting_default("player_backend", ifs, "files"); std::string backend = read_setting_default("player_backend", ifs, "files");
@ -24,14 +22,14 @@ PlayerAttributes::PlayerAttributes(const std::string &worldDir)
else if (backend == "sqlite3") else if (backend == "sqlite3")
readSqlite(worldDir + "players.sqlite"); readSqlite(worldDir + "players.sqlite");
else else
throw std::runtime_error(((std::string) "Unknown player backend: ") + backend); throw std::runtime_error(std::string("Unknown player backend: ") + backend);
} }
void PlayerAttributes::readFiles(const std::string &playersPath) void PlayerAttributes::readFiles(const std::string &playersPath)
{ {
DIR *dir; DIR *dir;
dir = opendir (playersPath.c_str()); dir = opendir (playersPath.c_str());
if (dir == NULL) if (!dir)
return; return;
struct dirent *ent; struct dirent *ent;
@ -39,18 +37,17 @@ void PlayerAttributes::readFiles(const std::string &playersPath)
if (ent->d_name[0] == '.') if (ent->d_name[0] == '.')
continue; continue;
string path = playersPath + PATH_SEPARATOR + ent->d_name; std::ifstream in(playersPath + PATH_SEPARATOR + ent->d_name);
ifstream in(path.c_str()); if (!in.good())
if(!in.good())
continue; continue;
string name, position; std::string name, position;
name = read_setting("name", in); name = read_setting("name", in);
in.seekg(0); in.seekg(0);
position = read_setting("position", in); position = read_setting("position", in);
Player player; Player player;
istringstream iss(position); std::istringstream iss(position);
char tmp; char tmp;
iss >> tmp; // '(' iss >> tmp; // '('
iss >> player.x; iss >> player.x;
@ -59,16 +56,17 @@ void PlayerAttributes::readFiles(const std::string &playersPath)
iss >> tmp; // ',' iss >> tmp; // ','
iss >> player.z; iss >> player.z;
iss >> tmp; // ')' iss >> tmp; // ')'
if(tmp != ')') if (tmp != ')')
continue; continue;
player.name = name; player.name = name;
player.x /= 10.0; player.x /= 10.0f;
player.y /= 10.0; player.y /= 10.0f;
player.z /= 10.0; player.z /= 10.0f;
m_players.push_back(player); m_players.push_back(player);
} }
closedir(dir); closedir(dir);
} }
@ -103,14 +101,14 @@ void PlayerAttributes::readSqlite(const std::string &db_name)
Player player; Player player;
const unsigned char *name_ = sqlite3_column_text(stmt_get_player_pos, 0); const unsigned char *name_ = sqlite3_column_text(stmt_get_player_pos, 0);
player.name = std::string(reinterpret_cast<const char*>(name_)); player.name = reinterpret_cast<const char*>(name_);
player.x = sqlite3_column_double(stmt_get_player_pos, 1); player.x = sqlite3_column_double(stmt_get_player_pos, 1);
player.y = sqlite3_column_double(stmt_get_player_pos, 2); player.y = sqlite3_column_double(stmt_get_player_pos, 2);
player.z = sqlite3_column_double(stmt_get_player_pos, 3); player.z = sqlite3_column_double(stmt_get_player_pos, 3);
player.x /= 10.0; player.x /= 10.0f;
player.y /= 10.0; player.y /= 10.0f;
player.z /= 10.0; player.z /= 10.0f;
m_players.push_back(player); m_players.push_back(player);
} }
@ -121,13 +119,13 @@ void PlayerAttributes::readSqlite(const std::string &db_name)
/**********/ /**********/
PlayerAttributes::Players::iterator PlayerAttributes::begin() PlayerAttributes::Players::const_iterator PlayerAttributes::begin() const
{ {
return m_players.begin(); return m_players.cbegin();
} }
PlayerAttributes::Players::iterator PlayerAttributes::end() PlayerAttributes::Players::const_iterator PlayerAttributes::end() const
{ {
return m_players.end(); return m_players.cend();
} }

View File

@ -1,31 +1,45 @@
Minetest Mapper C++ Minetest Mapper C++
=================== ===================
.. image:: https://travis-ci.org/minetest/minetestmapper.svg?branch=master .. image:: https://github.com/minetest/minetestmapper/workflows/build/badge.svg
:target: https://travis-ci.org/minetest/minetestmapper :target: https://github.com/minetest/minetestmapper/actions/workflows/build.yml
A port of minetestmapper.py to C++ from https://github.com/minetest/minetest/tree/master/util. Minetestmapper generates an overview image from a Minetest map.
This version is both faster and provides more features than the now deprecated Python script.
A port of minetestmapper.py to C++ from https://github.com/minetest/minetest/tree/0.4.17/util.
This version is both faster and provides more features than the now obsolete Python script.
Minetestmapper ships with a colors.txt file for Minetest Game, if you use a different game or have
many mods installed you should generate a matching colors.txt for better results.
The `generate_colorstxt.py script
<./util/generate_colorstxt.py>`_ in the util folder exists for this purpose, detailed instructions can be found within.
Requirements Requirements
------------ ------------
* C++ compiler, zlib, zstd
* libgd * libgd
* sqlite3 * sqlite3
* LevelDB (optional, set ENABLE_LEVELDB=1 in CMake to enable) * LevelDB (optional)
* hiredis library (optional, set ENABLE_REDIS=1 in CMake to enable) * hiredis (optional)
* Postgres libraries (optional, set ENABLE_POSTGRES=1 in CMake to enable) * Postgres libraries (optional)
e.g. on Debian: on Debian/Ubuntu:
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
sudo apt-get install libgd-dev libsqlite3-dev libleveldb-dev libhiredis-dev libpq-dev ``sudo apt install cmake libgd-dev libhiredis-dev libleveldb-dev libpq-dev libsqlite3-dev zlib1g-dev libzstd-dev``
Windows on openSUSE:
^^^^^^^ ^^^^^^^^^^^^
Minetestmapper for Windows can be downloaded here: https://github.com/minetest/minetestmapper/releases
After extracting the archive, minetestmapper can be invoked from cmd.exe: ``sudo zypper install gd-devel hiredis-devel leveldb-devel postgresql-devel sqlite3-devel zlib-devel libzstd-devel``
for Windows:
^^^^^^^^^^^^
Minetestmapper for Windows can be downloaded `from the Releases section
<https://github.com/minetest/minetestmapper/releases>`_.
After extracting the archive, it can be invoked from cmd.exe:
:: ::
cd C:\Users\yourname\Desktop\example\path cd C:\Users\yourname\Desktop\example\path
@ -37,7 +51,7 @@ Compilation
:: ::
cmake . -DENABLE_LEVELDB=1 cmake . -DENABLE_LEVELDB=1
make -j2 make -j$(nproc)
Usage Usage
----- -----
@ -106,3 +120,8 @@ colors:
scales: scales:
Draw scales on specified image edges (letters *t b l r* meaning top, bottom, left and right), e.g. ``--scales tbr`` 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,18 +1,23 @@
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <climits> #include <climits>
#include <cassert>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include <type_traits>
#include <limits>
#include "TileGenerator.h" #include "TileGenerator.h"
#include "config.h" #include "config.h"
#include "PlayerAttributes.h" #include "PlayerAttributes.h"
#include "BlockDecoder.h" #include "BlockDecoder.h"
#include "Image.h"
#include "util.h" #include "util.h"
#include "db-sqlite3.h" #include "db-sqlite3.h"
#if USE_POSTGRESQL #if USE_POSTGRESQL
#include "db-postgresql.h" #include "db-postgresql.h"
@ -24,18 +29,45 @@
#include "db-redis.h" #include "db-redis.h"
#endif #endif
using namespace std; #ifndef __has_builtin
#define __has_builtin(x) 0
#endif
template<typename T> // saturating multiplication
static inline T mymax(T a, T b) template<typename T, class = typename std::enable_if<std::is_unsigned<T>::value>::type>
inline T sat_mul(T a, T b)
{ {
return (a > b) ? a : b; #if __has_builtin(__builtin_mul_overflow)
T res;
if (__builtin_mul_overflow(a, b, &res))
return std::numeric_limits<T>::max();
return res;
#else
// WARNING: the fallback implementation is incorrect since we compute ceil(log(x)) not log(x)
// but that's good enough for our usecase...
const int bits = sizeof(T) * 8;
int hb_a = 0, hb_b = 0;
for (int i = bits - 1; i >= 0; i--) {
if (a & (static_cast<T>(1) << i)) {
hb_a = i; break;
}
}
for (int i = bits - 1; i >= 0; i--) {
if (b & (static_cast<T>(1) << i)) {
hb_b = i; break;
}
}
// log2(a) + log2(b) >= log2(MAX) <=> calculation will overflow
if (hb_a + hb_b >= bits)
return std::numeric_limits<T>::max();
return a * b;
#endif
} }
template<typename T> template<typename T>
static inline T mymin(T a, T b) inline T sat_mul(T a, T b, T c)
{ {
return (a > b) ? b : a; return sat_mul(sat_mul(a, b), c);
} }
// rounds n (away from 0) to a multiple of f while preserving the sign of n // rounds n (away from 0) to a multiple of f while preserving the sign of n
@ -50,11 +82,25 @@ static int round_multiple_nosign(int n, int f)
return sign * (abs_n + f - (abs_n % f)); return sign * (abs_n + f - (abs_n % f));
} }
static inline unsigned int colorSafeBounds (int channel) static inline unsigned int colorSafeBounds(int channel)
{ {
return mymin(mymax(channel, 0), 255); return mymin(mymax(channel, 0), 255);
} }
static Color parseColor(const std::string &color)
{
if (color.length() != 7)
throw std::runtime_error("Color needs to be 7 characters long");
if (color[0] != '#')
throw std::runtime_error("Color needs to begin with #");
unsigned long col = strtoul(color.c_str() + 1, NULL, 16);
u8 b, g, r;
b = col & 0xff;
g = (col >> 8) & 0xff;
r = (col >> 16) & 0xff;
return Color(r, g, b);
}
static Color mixColors(Color a, Color b) static Color mixColors(Color a, Color b)
{ {
Color result; Color result;
@ -89,14 +135,18 @@ TileGenerator::TileGenerator():
m_xMax(INT_MIN), m_xMax(INT_MIN),
m_zMin(INT_MAX), m_zMin(INT_MAX),
m_zMax(INT_MIN), m_zMax(INT_MIN),
m_yMin(-30000), m_yMin(INT16_MIN),
m_yMax(30000), m_yMax(INT16_MAX),
m_geomX(-2048), m_geomX(-2048),
m_geomY(-2048), m_geomY(-2048),
m_geomX2(2048), m_geomX2(2048),
m_geomY2(2048), m_geomY2(2048),
m_exhaustiveSearch(EXH_AUTO),
m_renderedAny(false),
m_zoom(1), m_zoom(1),
m_scales(SCALE_LEFT | SCALE_TOP) m_scales(SCALE_LEFT | SCALE_TOP),
m_progressMax(0),
m_progressLast(-1)
{ {
} }
@ -137,21 +187,6 @@ void TileGenerator::setScales(uint flags)
m_scales = flags; m_scales = flags;
} }
Color TileGenerator::parseColor(const std::string &color)
{
Color parsed;
if (color.length() != 7)
throw std::runtime_error("Color needs to be 7 characters long");
if (color[0] != '#')
throw std::runtime_error("Color needs to begin with #");
unsigned long col = strtoul(color.c_str() + 1, NULL, 16);
parsed.b = col & 0xff;
parsed.g = (col >> 8) & 0xff;
parsed.r = (col >> 16) & 0xff;
parsed.a = 255;
return parsed;
}
void TileGenerator::setDrawOrigin(bool drawOrigin) void TileGenerator::setDrawOrigin(bool drawOrigin)
{ {
m_drawOrigin = drawOrigin; m_drawOrigin = drawOrigin;
@ -184,6 +219,7 @@ void TileGenerator::setBackend(std::string backend)
void TileGenerator::setGeometry(int x, int y, int w, int h) 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_geomX = round_multiple_nosign(x, 16) / 16;
m_geomY = round_multiple_nosign(y, 16) / 16; m_geomY = round_multiple_nosign(y, 16) / 16;
m_geomX2 = round_multiple_nosign(x + w, 16) / 16; m_geomX2 = round_multiple_nosign(x + w, 16) / 16;
@ -193,29 +229,38 @@ void TileGenerator::setGeometry(int x, int y, int w, int h)
void TileGenerator::setMinY(int y) void TileGenerator::setMinY(int y)
{ {
m_yMin = y; m_yMin = y;
if (m_yMin > m_yMax)
std::swap(m_yMin, m_yMax);
} }
void TileGenerator::setMaxY(int y) void TileGenerator::setMaxY(int y)
{ {
m_yMax = 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::setDontWriteEmpty(bool f)
{
m_dontWriteEmpty = f;
} }
void TileGenerator::parseColorsFile(const std::string &fileName) void TileGenerator::parseColorsFile(const std::string &fileName)
{ {
ifstream in; std::ifstream in(fileName);
in.open(fileName.c_str(), ifstream::in); if (!in.good())
if (!in.is_open())
throw std::runtime_error("Specified colors file could not be found"); throw std::runtime_error("Specified colors file could not be found");
parseColorsStream(in); parseColorsStream(in);
} }
void TileGenerator::printGeometry(const std::string &input) void TileGenerator::printGeometry(const std::string &input_path)
{ {
string input_path = input; setExhaustiveSearch(EXH_NEVER);
if (input_path[input.length() - 1] != PATH_SEPARATOR) {
input_path += PATH_SEPARATOR;
}
openDb(input_path); openDb(input_path);
loadBlocks(); loadBlocks();
@ -226,25 +271,34 @@ void TileGenerator::printGeometry(const std::string &input)
<< std::endl; << std::endl;
closeDatabase(); closeDatabase();
} }
void TileGenerator::setDontWriteEmpty(bool f) void TileGenerator::dumpBlock(const std::string &input_path, BlockPos pos)
{ {
m_dontWriteEmpty = f; openDb(input_path);
}
void TileGenerator::generate(const std::string &input, const std::string &output) BlockList list;
{ std::vector<BlockPos> positions;
string input_path = input; positions.emplace_back(pos);
if (input_path[input.length() - 1] != PATH_SEPARATOR) { m_db->getBlocksByPos(list, positions);
input_path += PATH_SEPARATOR; if (!list.empty()) {
const ustring &data = list.begin()->second;
for (u8 c : data)
printf("%02x", static_cast<int>(c));
printf("\n");
} }
closeDatabase();
}
void TileGenerator::generate(const std::string &input_path, const std::string &output)
{
if (m_dontWriteEmpty) // FIXME: possible too, just needs to be done differently
setExhaustiveSearch(EXH_NEVER);
openDb(input_path); openDb(input_path);
loadBlocks(); loadBlocks();
if (m_dontWriteEmpty && ! m_positions.size()) if (m_dontWriteEmpty && m_positions.empty())
{ {
closeDatabase(); closeDatabase();
return; return;
@ -268,61 +322,104 @@ void TileGenerator::generate(const std::string &input, const std::string &output
void TileGenerator::parseColorsStream(std::istream &in) void TileGenerator::parseColorsStream(std::istream &in)
{ {
char line[128]; char line[512];
while (in.good()) { while (in.good()) {
in.getline(line, 128); in.getline(line, sizeof(line));
for(char *p = line; *p; p++) { for (char *p = line; *p; p++) {
if(*p != '#') if (*p != '#')
continue; continue;
*p = '\0'; // Cut off at the first # *p = '\0'; // Cut off at the first #
break; break;
} }
if(strlen(line) == 0) if(!line[0])
continue; continue;
char name[64 + 1]; char name[200 + 1] = {0};
unsigned int r, g, b, a, t; unsigned int r, g, b, a = 255, t = 0;
a = 255; int items = sscanf(line, "%200s %u %u %u %u %u", name, &r, &g, &b, &a, &t);
t = 0; if (items < 4) {
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; std::cerr << "Failed to parse color entry '" << line << "'" << std::endl;
continue; continue;
} }
ColorEntry color = ColorEntry(r, g, b, a, t); m_colorMap[name] = ColorEntry(r, g, b, a, t);
m_colorMap[name] = color;
} }
} }
void TileGenerator::openDb(const std::string &input) 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_path)
{
std::string input = input_path;
if (input.back() != PATH_SEPARATOR)
input += PATH_SEPARATOR;
std::string backend = m_backend; std::string backend = m_backend;
if(backend == "") { if (backend.empty()) {
std::ifstream ifs((input + "/world.mt").c_str()); std::ifstream ifs(input + "world.mt");
if(!ifs.good()) if(!ifs.good())
throw std::runtime_error("Failed to read world.mt"); throw std::runtime_error("Failed to open world.mt");
backend = read_setting("backend", ifs); backend = read_setting_default("backend", ifs, "sqlite3");
ifs.close(); ifs.close();
} }
if(backend == "sqlite3") if (backend == "sqlite3")
m_db = new DBSQLite3(input); m_db = new DBSQLite3(input);
#if USE_POSTGRESQL #if USE_POSTGRESQL
else if(backend == "postgresql") else if (backend == "postgresql")
m_db = new DBPostgreSQL(input); m_db = new DBPostgreSQL(input);
#endif #endif
#if USE_LEVELDB #if USE_LEVELDB
else if(backend == "leveldb") else if (backend == "leveldb")
m_db = new DBLevelDB(input); m_db = new DBLevelDB(input);
#endif #endif
#if USE_REDIS #if USE_REDIS
else if(backend == "redis") else if (backend == "redis")
m_db = new DBRedis(input); m_db = new DBRedis(input);
#endif #endif
else else
throw std::runtime_error(((std::string) "Unknown map backend: ") + backend); 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) {
size_t y_range = (m_yMax / 16 + 1) - (m_yMin / 16);
size_t blocks = sat_mul<size_t>(m_geomX2 - m_geomX, y_range, m_geomY2 - m_geomY);
#ifndef NDEBUG
std::cerr << "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() void TileGenerator::closeDatabase()
@ -331,31 +428,52 @@ void TileGenerator::closeDatabase()
m_db = NULL; m_db = NULL;
} }
static inline int16_t mod16(int16_t y)
{
if (y < 0)
return (y - 15) / 16;
return y / 16;
}
void TileGenerator::loadBlocks() void TileGenerator::loadBlocks()
{ {
std::vector<BlockPos> vec = m_db->getBlockPos(); const int16_t yMax = mod16(m_yMax) + 1;
for (std::vector<BlockPos>::iterator it = vec.begin(); it != vec.end(); ++it) { const int16_t yMin = mod16(m_yMin);
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 (pos.z < m_zMin) if (m_exhaustiveSearch == EXH_NEVER || m_exhaustiveSearch == EXH_Y) {
m_zMin = pos.z; std::vector<BlockPos> vec = m_db->getBlockPos(
if (pos.z > m_zMax) BlockPos(m_geomX, yMin, m_geomY),
m_zMax = pos.z; BlockPos(m_geomX2, yMax, m_geomY2)
m_positions.push_back(std::pair<int, int>(pos.x, pos.z)); );
for (auto pos : vec) {
assert(pos.x >= m_geomX && pos.x < m_geomX2);
assert(pos.y >= yMin && 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);
}
size_t count = 0;
for (const auto &it : m_positions)
count += it.second.size();
m_progressMax = count;
#ifndef NDEBUG
std::cerr << "Loaded " << count
<< " positions (across Z: " << m_positions.size() << ") for rendering" << std::endl;
#endif
} }
m_positions.sort();
m_positions.unique();
} }
void TileGenerator::createImage() void TileGenerator::createImage()
@ -364,7 +482,6 @@ void TileGenerator::createImage()
if(!m_drawScale) if(!m_drawScale)
m_scales = 0; m_scales = 0;
// If a geometry is explicitly set, set the bounding box to the requested geometry // 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 // instead of cropping to the content. This way we will always output a full tile
// of the correct size. // of the correct size.
@ -393,10 +510,11 @@ void TileGenerator::createImage()
image_height = (m_mapHeight * m_zoom) + m_yBorder; image_height = (m_mapHeight * m_zoom) + m_yBorder;
image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 0; image_height += (m_scales & SCALE_BOTTOM) ? scale_d : 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!" std::cerr << "Warning: The width or height of the image to be created exceeds 4096 pixels!"
<< " (Dimensions: " << image_width << "x" << image_height << ")" << " (Dimensions: " << image_width << "x" << image_height << ")"
<< std::endl; << std::endl;
}
m_image = new Image(image_width, image_height); m_image = new Image(image_width, image_height);
m_image->drawFilledRect(0, 0, image_width, image_height, m_bgColor); // Background m_image->drawFilledRect(0, 0, image_width, image_height, m_bgColor); // Background
} }
@ -404,47 +522,114 @@ void TileGenerator::createImage()
void TileGenerator::renderMap() void TileGenerator::renderMap()
{ {
BlockDecoder blk; BlockDecoder blk;
std::list<int> zlist = getZValueList(); const int16_t yMax = mod16(m_yMax) + 1;
for (std::list<int>::iterator zPosition = zlist.begin(); zPosition != zlist.end(); ++zPosition) { const int16_t yMin = mod16(m_yMin);
int zPos = *zPosition; size_t count = 0;
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;
m_readPixels.reset(); auto renderSingle = [&] (int16_t xPos, int16_t zPos, BlockList &blockStack) {
m_readInfo.reset(); m_readPixels.reset();
for (int i = 0; i < 16; i++) { m_readInfo.reset();
for (int j = 0; j < 16; j++) { for (int i = 0; i < 16; i++) {
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 for (int j = 0; j < 16; j++) {
m_color[i][j].a = 0; // ..but set alpha to 0 to tell renderMapBlock() not to use this color to mix a shade 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_thickness[i][j] = 0; 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;
blk.reset();
blk.decode(it->second);
if (blk.isEmpty())
continue;
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);
} }
for (const auto &it : blockStack) {
const BlockPos pos = it.first;
assert(pos.x == xPos && pos.z == zPos);
assert(pos.y >= yMin && pos.y < yMax);
blk.reset();
blk.decode(it.second);
if (blk.isEmpty())
continue;
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);
m_renderedAny |= m_readInfo.any();
};
auto postRenderRow = [&] (int16_t zPos) {
if (m_shading) if (m_shading)
renderShading(zPos); 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, yMin, yMax);
blockStack.sort();
renderSingle(xPos, zPos, blockStack);
reportProgress(count++);
}
postRenderRow(zPos);
}
} else if (m_exhaustiveSearch == EXH_Y) {
#ifndef NDEBUG
std::cerr << "Exhaustively searching height of "
<< (yMax - yMin) << " blocks" << std::endl;
#endif
std::vector<BlockPos> positions;
positions.reserve(yMax - yMin);
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 = yMin; yPos < yMax; yPos++)
positions.emplace_back(xPos, yPos, zPos);
BlockList blockStack;
m_db->getBlocksByPos(blockStack, positions);
blockStack.sort();
renderSingle(xPos, zPos, blockStack);
reportProgress(count++);
}
postRenderRow(zPos);
}
} else if (m_exhaustiveSearch == EXH_FULL) {
const size_t span_y = yMax - yMin;
m_progressMax = (m_geomX2 - m_geomX) * span_y * (m_geomY2 - m_geomY);
#ifndef NDEBUG
std::cerr << "Exhaustively searching "
<< (m_geomX2 - m_geomX) << "x" << span_y << "x"
<< (m_geomY2 - m_geomY) << " blocks" << std::endl;
#endif
std::vector<BlockPos> positions;
positions.reserve(span_y);
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 = yMin; yPos < yMax; yPos++)
positions.emplace_back(xPos, yPos, zPos);
BlockList blockStack;
m_db->getBlocksByPos(blockStack, positions);
blockStack.sort();
renderSingle(xPos, zPos, blockStack);
reportProgress(count++);
}
postRenderRow(zPos);
}
} }
reportProgress(m_progressMax);
} }
void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos) void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos)
@ -452,46 +637,50 @@ void TileGenerator::renderMapBlock(const BlockDecoder &blk, const BlockPos &pos)
int xBegin = (pos.x - m_xMin) * 16; int xBegin = (pos.x - m_xMin) * 16;
int zBegin = (m_zMax - pos.z) * 16; int zBegin = (m_zMax - pos.z) * 16;
int minY = (pos.y * 16 > m_yMin) ? 0 : m_yMin - pos.y * 16; int minY = (pos.y * 16 > m_yMin) ? 0 : m_yMin - pos.y * 16;
int maxY = (pos.y * 16 < m_yMax) ? 15 : m_yMax - pos.y * 16; int maxY = (pos.y * 16 + 15 < m_yMax) ? 15 : m_yMax - pos.y * 16;
for (int z = 0; z < 16; ++z) { for (int z = 0; z < 16; ++z) {
int imageY = zBegin + 15 - z; int imageY = zBegin + 15 - z;
for (int x = 0; x < 16; ++x) { for (int x = 0; x < 16; ++x) {
if (m_readPixels.get(x, z)) if (m_readPixels.get(x, z))
continue; continue;
int imageX = xBegin + x; int imageX = xBegin + x;
auto &attr = m_blockPixelAttributes.attribute(15 - z, xBegin + x);
for (int y = maxY; y >= minY; --y) { for (int y = maxY; y >= minY; --y) {
string name = blk.getNode(x, y, z); const std::string &name = blk.getNode(x, y, z);
if (name == "") if (name.empty())
continue; continue;
ColorMap::const_iterator it = m_colorMap.find(name); ColorMap::const_iterator it = m_colorMap.find(name);
if (it == m_colorMap.end()) { if (it == m_colorMap.end()) {
m_unknownNodes.insert(name); m_unknownNodes.insert(name);
continue; continue;
} }
const Color c = it->second.to_color();
Color c = it->second.toColor();
if (c.a == 0)
continue; // node is fully invisible
if (m_drawAlpha) { if (m_drawAlpha) {
if (m_color[z][x].a == 0) if (m_color[z][x].a != 0)
m_color[z][x] = c; // first visible time, no color mixing c = mixColors(m_color[z][x], c);
else if (c.a < 255) {
m_color[z][x] = mixColors(m_color[z][x], c); // remember color and near thickness value
if(m_color[z][x].a < 0xff) { m_color[z][x] = c;
// near thickness value to thickness of current node m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2;
m_thickness[z][x] = (m_thickness[z][x] + it->second.t) / 2.0;
continue; continue;
} }
// color became opaque, draw it // color became opaque, draw it
setZoomed(imageX, imageY, m_color[z][x]); setZoomed(imageX, imageY, c);
m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x]; attr.thickness = m_thickness[z][x];
} else { } else {
setZoomed(imageX, imageY, c.noAlpha()); c.a = 255;
setZoomed(imageX, imageY, c);
} }
m_readPixels.set(x, z); m_readPixels.set(x, z);
// do this afterwards so we can record height values // do this afterwards so we can record height values
// inside transparent nodes (water) too // inside transparent nodes (water) too
if (!m_readInfo.get(x, z)) { if (!m_readInfo.get(x, z)) {
m_blockPixelAttributes.attribute(15 - z, xBegin + x).height = pos.y * 16 + y; attr.height = pos.y * 16 + y;
m_readInfo.set(x, z); m_readInfo.set(x, z);
} }
break; break;
@ -513,17 +702,19 @@ void TileGenerator::renderMapBlockBottom(const BlockPos &pos)
if (m_readPixels.get(x, z)) if (m_readPixels.get(x, z))
continue; continue;
int imageX = xBegin + x; int imageX = xBegin + x;
auto &attr = m_blockPixelAttributes.attribute(15 - z, xBegin + x);
// set color since it wasn't done in renderMapBlock() // set color since it wasn't done in renderMapBlock()
setZoomed(imageX, imageY, m_color[z][x]); setZoomed(imageX, imageY, m_color[z][x]);
m_readPixels.set(x, z); m_readPixels.set(x, z);
m_blockPixelAttributes.attribute(15 - z, xBegin + x).thickness = m_thickness[z][x]; attr.thickness = m_thickness[z][x];
} }
} }
} }
void TileGenerator::renderShading(int zPos) void TileGenerator::renderShading(int zPos)
{ {
auto &a = m_blockPixelAttributes;
int zBegin = (m_zMax - zPos) * 16; int zBegin = (m_zMax - zPos) * 16;
for (int z = 0; z < 16; ++z) { for (int z = 0; z < 16; ++z) {
int imageY = zBegin + z; int imageY = zBegin + z;
@ -531,23 +722,27 @@ void TileGenerator::renderShading(int zPos)
continue; continue;
for (int x = 0; x < m_mapWidth; ++x) { for (int x = 0; x < m_mapWidth; ++x) {
if( if(
!m_blockPixelAttributes.attribute(z, x).valid_height() || !a.attribute(z, x).valid_height() ||
!m_blockPixelAttributes.attribute(z, x - 1).valid_height() || !a.attribute(z, x - 1).valid_height() ||
!m_blockPixelAttributes.attribute(z - 1, x).valid_height() !a.attribute(z - 1, x).valid_height()
) )
continue; continue;
// calculate shadow to apply // calculate shadow to apply
int y = m_blockPixelAttributes.attribute(z, x).height; int y = a.attribute(z, x).height;
int y1 = m_blockPixelAttributes.attribute(z, x - 1).height; int y1 = a.attribute(z, x - 1).height;
int y2 = m_blockPixelAttributes.attribute(z - 1, x).height; int y2 = a.attribute(z - 1, x).height;
int d = ((y - y1) + (y - y2)) * 12; int d = ((y - y1) + (y - y2)) * 12;
if (m_drawAlpha) { // less visible shadow with increasing "thickness" if (m_drawAlpha) { // less visible shadow with increasing "thickness"
double t = m_blockPixelAttributes.attribute(z, x).thickness * 1.2; float t = a.attribute(z, x).thickness * 1.2f;
d *= 1.0 - mymin(t, 255.0) / 255.0; t = mymin(t, 255.0f);
d *= 1.0f - t / 255.0f;
} }
d = mymin(d, 36); d = mymin(d, 36);
// apply shadow/light by just adding to it pixel values
Color c = m_image->getPixel(getImageX(x), getImageY(imageY)); Color c = m_image->getPixel(getImageX(x), getImageY(imageY));
c.r = colorSafeBounds(c.r + d); c.r = colorSafeBounds(c.r + d);
c.g = colorSafeBounds(c.g + d); c.g = colorSafeBounds(c.g + d);
@ -555,7 +750,7 @@ void TileGenerator::renderShading(int zPos)
setZoomed(x, imageY, c); setZoomed(x, imageY, c);
} }
} }
m_blockPixelAttributes.scroll(); a.scroll();
} }
void TileGenerator::renderScale() void TileGenerator::renderScale()
@ -633,49 +828,68 @@ void TileGenerator::renderOrigin()
m_image->drawCircle(getImageX(0, true), getImageY(0, true), 12, m_originColor); m_image->drawCircle(getImageX(0, true), getImageY(0, true), 12, m_originColor);
} }
void TileGenerator::renderPlayers(const std::string &inputPath) void TileGenerator::renderPlayers(const std::string &input_path)
{ {
PlayerAttributes players(inputPath); std::string input = input_path;
for (PlayerAttributes::Players::iterator player = players.begin(); player != players.end(); ++player) { if (input.back() != PATH_SEPARATOR)
if (player->x < m_xMin * 16 || player->x > m_xMax * 16 || input += PATH_SEPARATOR;
player->z < m_zMin * 16 || player->z > m_zMax * 16)
PlayerAttributes players(input);
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; continue;
if (player->y < m_yMin || player->y > m_yMax) if (player.y < m_yMin || player.y > m_yMax)
continue; continue;
int imageX = getImageX(player->x, true), int imageX = getImageX(player.x, true),
imageY = getImageY(player->z, true); imageY = getImageY(player.z, true);
m_image->drawFilledRect(imageX - 1, imageY, 3, 1, m_playerColor); m_image->drawFilledRect(imageX - 1, imageY, 3, 1, m_playerColor);
m_image->drawFilledRect(imageX, imageY - 1, 1, 3, m_playerColor); m_image->drawFilledRect(imageX, imageY - 1, 1, 3, m_playerColor);
m_image->drawText(imageX + 2, imageY, player->name, m_playerColor); m_image->drawText(imageX + 2, imageY, 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) void TileGenerator::writeImage(const std::string &output)
{ {
m_image->save(output); m_image->save(output);
delete m_image; delete m_image;
m_image = NULL; m_image = nullptr;
} }
void TileGenerator::printUnknown() void TileGenerator::printUnknown()
{ {
if (m_unknownNodes.size() == 0) if (m_unknownNodes.empty())
return; return;
std::cerr << "Unknown nodes:" << std::endl; std::cerr << "Unknown nodes:" << std::endl;
for (NameSet::iterator node = m_unknownNodes.begin(); node != m_unknownNodes.end(); ++node) for (const auto &node : m_unknownNodes)
std::cerr << "\t" << *node << std::endl; std::cerr << "\t" << node << std::endl;
if (!m_renderedAny) {
std::cerr << "The map was read successfully and not empty, but none of the "
"encountered nodes had a color associated.\nCheck that you're using "
"the right colors.txt. It should match the game you have installed." << std::endl;
}
}
void TileGenerator::reportProgress(size_t count)
{
if (!m_progressMax)
return;
int percent = count / static_cast<float>(m_progressMax) * 100;
if (percent == m_progressLast)
return;
m_progressLast = percent;
// Print a nice-looking ASCII progress bar
char bar[51] = {0};
memset(bar, ' ', 50);
int i = 0, j = percent;
for (; j >= 2; j -= 2)
bar[i++] = '=';
if (j)
bar[i++] = '-';
std::cout << "[" << bar << "] " << percent << "% " << (percent == 100 ? "\n" : "\r");
std::cout.flush();
} }
inline int TileGenerator::getImageX(int val, bool absolute) const inline int TileGenerator::getImageX(int val, bool absolute) const

View File

@ -1,17 +1,8 @@
/*
* =====================================================================
* Version: 1.0
* Created: 18.09.2012 10:20:47
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#include <zlib.h> #include <zlib.h>
#include <stdint.h> #include <stdint.h>
#include "ZlibDecompressor.h" #include "ZlibDecompressor.h"
ZlibDecompressor::ZlibDecompressor(const unsigned char *data, std::size_t size): ZlibDecompressor::ZlibDecompressor(const u8 *data, size_t size):
m_data(data), m_data(data),
m_seekPos(0), m_seekPos(0),
m_size(size) m_size(size)
@ -22,12 +13,12 @@ ZlibDecompressor::~ZlibDecompressor()
{ {
} }
void ZlibDecompressor::setSeekPos(std::size_t seekPos) void ZlibDecompressor::setSeekPos(size_t seekPos)
{ {
m_seekPos = seekPos; m_seekPos = seekPos;
} }
std::size_t ZlibDecompressor::seekPos() const size_t ZlibDecompressor::seekPos() const
{ {
return m_seekPos; return m_seekPos;
} }
@ -35,36 +26,43 @@ std::size_t ZlibDecompressor::seekPos() const
ustring ZlibDecompressor::decompress() ustring ZlibDecompressor::decompress()
{ {
const unsigned char *data = m_data + m_seekPos; const unsigned char *data = m_data + m_seekPos;
const std::size_t size = m_size - m_seekPos; const size_t size = m_size - m_seekPos;
ustring buffer; ustring buffer;
const size_t BUFSIZE = 128 * 1024; constexpr size_t BUFSIZE = 32 * 1024;
uint8_t temp_buffer[BUFSIZE];
z_stream strm; z_stream strm;
strm.zalloc = Z_NULL; strm.zalloc = Z_NULL;
strm.zfree = Z_NULL; strm.zfree = Z_NULL;
strm.opaque = Z_NULL; strm.opaque = Z_NULL;
strm.next_in = Z_NULL; strm.next_in = Z_NULL;
strm.avail_in = size; strm.avail_in = 0;
if (inflateInit(&strm) != Z_OK) { if (inflateInit(&strm) != Z_OK)
throw DecompressError(); throw DecompressError();
}
strm.next_in = const_cast<unsigned char *>(data); strm.next_in = const_cast<unsigned char *>(data);
strm.avail_in = size;
buffer.resize(BUFSIZE);
strm.next_out = &buffer[0];
strm.avail_out = BUFSIZE;
int ret = 0; int ret = 0;
do { do {
strm.avail_out = BUFSIZE;
strm.next_out = temp_buffer;
ret = inflate(&strm, Z_NO_FLUSH); ret = inflate(&strm, Z_NO_FLUSH);
buffer += ustring(reinterpret_cast<unsigned char *>(temp_buffer), BUFSIZE - strm.avail_out); if (strm.avail_out == 0) {
const auto off = buffer.size();
buffer.reserve(off + BUFSIZE);
strm.next_out = &buffer[off];
strm.avail_out = BUFSIZE;
}
} while (ret == Z_OK); } while (ret == Z_OK);
if (ret != Z_STREAM_END) { if (ret != Z_STREAM_END)
throw DecompressError(); throw DecompressError();
}
m_seekPos += strm.next_in - data; m_seekPos += strm.next_in - data;
(void)inflateEnd(&strm); buffer.resize(buffer.size() - strm.avail_out);
(void) inflateEnd(&strm);
return buffer; return buffer;
} }

58
ZstdDecompressor.cpp Normal file
View File

@ -0,0 +1,58 @@
#include <zstd.h>
#include "ZstdDecompressor.h"
ZstdDecompressor::ZstdDecompressor():
m_data(nullptr),
m_seekPos(0),
m_size(0)
{
m_stream = ZSTD_createDStream();
}
ZstdDecompressor::~ZstdDecompressor()
{
ZSTD_freeDStream(reinterpret_cast<ZSTD_DStream*>(m_stream));
}
void ZstdDecompressor::setData(const u8 *data, size_t size, size_t seekPos)
{
m_data = data;
m_seekPos = seekPos;
m_size = size;
}
std::size_t ZstdDecompressor::seekPos() const
{
return m_seekPos;
}
ustring ZstdDecompressor::decompress()
{
ZSTD_DStream *stream = reinterpret_cast<ZSTD_DStream*>(m_stream);
ZSTD_inBuffer inbuf = { m_data, m_size, m_seekPos };
ustring buffer;
constexpr size_t BUFSIZE = 32 * 1024;
buffer.resize(BUFSIZE);
ZSTD_outBuffer outbuf = { &buffer[0], buffer.size(), 0 };
ZSTD_initDStream(stream);
size_t ret;
do {
ret = ZSTD_decompressStream(stream, &outbuf, &inbuf);
if (outbuf.size == outbuf.pos) {
outbuf.size += BUFSIZE;
buffer.resize(outbuf.size);
outbuf.dst = &buffer[0];
}
if (ret && ZSTD_isError(ret))
throw DecompressError();
} while (ret != 0);
m_seekPos = inbuf.pos;
buffer.resize(outbuf.pos);
return buffer;
}

View File

@ -1,132 +0,0 @@
==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
end
return tile
end
local function pairs_s(dict)
local keys = {}
for k in pairs(dict) do
table.insert(keys, k)
end
table.sort(keys)
return ipairs(keys)
end
minetest.register_chatcommand("dumpnodes", {
params = "",
description = "",
func = function(player, 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
print("ignored(1): " .. nn)
else
if ntbl[prefix] == nil then
ntbl[prefix] = {}
end
ntbl[prefix][name] = true
end
end
local out, err = io.open('nodes.txt', 'wb')
if not out then
return true, "io.open(): " .. err
end
for _, prefix in pairs_s(ntbl) do
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')
n = n + 1
end
end
out:write('\n')
end
out:close()
return true, n .. " nodes dumped."
end,
})
==FILE== avgcolor.py
#!/usr/bin/env python
import sys
from math import sqrt
from PIL import Image
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')
ind = inp.load()
cl = ([], [], [])
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 len(cl[0]) == 0:
print("Didn't find average color for %s" % sys.argv[1], file=sys.stderr)
print("0 0 0")
else:
cl = tuple(sqrt(sum(x)/len(x)) for x 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
NODESTXT_PATH=./nodes.txt
COLORSTXT_PATH=./colors.txt
while read -r line; do
set -- junk $line; shift
if [[ -z "$1" || $1 == "#" ]]; then
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")
if [ -z "$tex" ]; then
echo "skip $1: texture not found" >&2
continue
fi
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:
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
==INSTRUCTIONS==
1) Make sure avgcolors.py works (outputs the usage instructions when run)
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)

24
cmake/FindZstd.cmake Normal file
View File

@ -0,0 +1,24 @@
mark_as_advanced(ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)
find_library(ZSTD_LIBRARY NAMES zstd)
if(ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY)
# Check that the API we use exists
include(CheckSymbolExists)
unset(HAVE_ZSTD_INITDSTREAM CACHE)
set(CMAKE_REQUIRED_INCLUDES ${ZSTD_INCLUDE_DIR})
set(CMAKE_REQUIRED_LIBRARIES ${ZSTD_LIBRARY})
check_symbol_exists(ZSTD_initDStream zstd.h HAVE_ZSTD_INITDSTREAM)
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES)
if(NOT HAVE_ZSTD_INITDSTREAM)
unset(ZSTD_INCLUDE_DIR CACHE)
unset(ZSTD_LIBRARY CACHE)
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Zstd DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR)

View File

@ -10,7 +10,7 @@ bones:bones 117 117 117
# butterflies # butterflies
# carts # carts
carts:brakerail 138 121 102 carts:brakerail 150 121 102
carts:powerrail 160 145 102 carts:powerrail 160 145 102
carts:rail 146 128 108 carts:rail 146 128 108
@ -117,7 +117,11 @@ default:marram_grass_1 113 139 96
default:marram_grass_2 102 131 90 default:marram_grass_2 102 131 90
default:marram_grass_3 99 130 88 default:marram_grass_3 99 130 88
default:mese 222 222 0 default:mese 222 222 0
default:mese_post_light 134 105 59 default:mese_post_light 132 103 57
default:mese_post_light_acacia_wood 151 62 39
default:mese_post_light_aspen_wood 210 199 170
default:mese_post_light_junglewood 57 39 14
default:mese_post_light_pine_wood 221 185 131
default:meselamp 213 215 143 default:meselamp 213 215 143
default:mossycobble 88 91 73 default:mossycobble 88 91 73
default:obsidian 21 24 29 default:obsidian 21 24 29
@ -174,12 +178,20 @@ default:wood 131 102 57
# doors # doors
doors:door_glass_a 245 245 245 64 16 doors:door_glass_a 245 245 245 64 16
doors:door_glass_b 245 245 245 64 16 doors:door_glass_b 245 245 245 64 16
doors:door_glass_c 245 245 245 64 16
doors:door_glass_d 245 245 245 64 16
doors:door_obsidian_glass_a 48 49 50 64 16 doors:door_obsidian_glass_a 48 49 50 64 16
doors:door_obsidian_glass_b 48 49 50 64 16 doors:door_obsidian_glass_b 48 49 50 64 16
doors:door_obsidian_glass_c 48 49 50 64 16
doors:door_obsidian_glass_d 48 49 50 64 16
doors:door_steel_a 203 203 203 doors:door_steel_a 203 203 203
doors:door_steel_b 203 203 203 doors:door_steel_b 203 203 203
doors:door_steel_c 203 203 203
doors:door_steel_d 203 203 203
doors:door_wood_a 89 68 37 doors:door_wood_a 89 68 37
doors:door_wood_b 89 68 37 doors:door_wood_b 89 68 37
doors:door_wood_c 89 68 37
doors:door_wood_d 89 68 37
doors:gate_acacia_wood_closed 150 61 39 doors:gate_acacia_wood_closed 150 61 39
doors:gate_acacia_wood_open 150 61 39 doors:gate_acacia_wood_open 150 61 39
doors:gate_aspen_wood_closed 210 199 170 doors:gate_aspen_wood_closed 210 199 170
@ -204,6 +216,7 @@ farming:cotton_5 116 105 53
farming:cotton_6 121 95 59 farming:cotton_6 121 95 59
farming:cotton_7 94 70 37 farming:cotton_7 94 70 37
farming:cotton_8 122 108 93 farming:cotton_8 122 108 93
farming:cotton_wild 111 111 101
farming:desert_sand_soil 161 132 72 farming:desert_sand_soil 161 132 72
farming:desert_sand_soil_wet 120 99 53 farming:desert_sand_soil_wet 120 99 53
farming:dry_soil 178 136 90 farming:dry_soil 178 136 90
@ -432,6 +445,8 @@ xpanes:bar 114 114 114 64 16
xpanes:bar_flat 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_a 133 133 133 64 16
xpanes:door_steel_bar_b 133 133 133 64 16 xpanes:door_steel_bar_b 133 133 133 64 16
xpanes:door_steel_bar_c 133 133 133 64 16
xpanes:door_steel_bar_d 133 133 133 64 16
xpanes:obsidian_pane 16 17 18 64 16 xpanes:obsidian_pane 16 17 18 64 16
xpanes:obsidian_pane_flat 16 17 18 64 16 xpanes:obsidian_pane_flat 16 17 18 64 16
xpanes:pane 249 249 249 64 16 xpanes:pane 249 249 249 64 16

View File

@ -5,13 +5,12 @@
static inline int64_t stoi64(const std::string &s) static inline int64_t stoi64(const std::string &s)
{ {
std::stringstream tmp(s); std::istringstream tmp(s);
int64_t t; int64_t t;
tmp >> t; tmp >> t;
return t; return t;
} }
static inline std::string i64tos(int64_t i) static inline std::string i64tos(int64_t i)
{ {
std::ostringstream os; std::ostringstream os;
@ -19,6 +18,7 @@ static inline std::string i64tos(int64_t i)
return os.str(); return os.str();
} }
DBLevelDB::DBLevelDB(const std::string &mapdir) DBLevelDB::DBLevelDB(const std::string &mapdir)
{ {
leveldb::Options options; leveldb::Options options;
@ -28,6 +28,9 @@ DBLevelDB::DBLevelDB(const std::string &mapdir)
throw std::runtime_error(std::string("Failed to open Database: ") + status.ToString()); 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(); loadPosCache();
} }
@ -38,9 +41,21 @@ DBLevelDB::~DBLevelDB()
} }
std::vector<BlockPos> DBLevelDB::getBlockPos() std::vector<BlockPos> DBLevelDB::getBlockPos(BlockPos min, BlockPos max)
{ {
return posCache; 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;
} }
@ -49,26 +64,51 @@ void DBLevelDB::loadPosCache()
leveldb::Iterator * it = db->NewIterator(leveldb::ReadOptions()); leveldb::Iterator * it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) { for (it->SeekToFirst(); it->Valid(); it->Next()) {
int64_t posHash = stoi64(it->key().ToString()); int64_t posHash = stoi64(it->key().ToString());
posCache.push_back(decodeBlockPos(posHash)); BlockPos pos = decodeBlockPos(posHash);
posCache[pos.z].emplace_back(pos.x, pos.y);
} }
delete it; delete it;
} }
void DBLevelDB::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) void DBLevelDB::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y)
{ {
std::string datastr; std::string datastr;
leveldb::Status status; leveldb::Status status;
for (std::vector<BlockPos>::iterator it = posCache.begin(); it != posCache.end(); ++it) { auto it = posCache.find(z);
if (it->z != zPos) { if (it == posCache.cend())
return;
for (auto pos2 : it->second) {
if (pos2.first != x)
continue; continue;
} if (pos2.second < min_y || pos2.second >= max_y)
status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(*it)), &datastr); continue;
BlockPos pos(x, pos2.second, z);
status = db->Get(leveldb::ReadOptions(), i64tos(encodeBlockPos(pos)), &datastr);
if (status.ok()) { if (status.ok()) {
Block b(*it, ustring((const unsigned char *) datastr.data(), datastr.size())); blocks.emplace_back(
blocks[b.first.x].push_back(b); pos, ustring((unsigned char *) datastr.data(), datastr.size())
);
} }
} }
} }
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())
);
}
}
}

View File

@ -11,8 +11,8 @@
DBPostgreSQL::DBPostgreSQL(const std::string &mapdir) DBPostgreSQL::DBPostgreSQL(const std::string &mapdir)
{ {
std::ifstream ifs((mapdir + "/world.mt").c_str()); std::ifstream ifs(mapdir + "world.mt");
if(!ifs.good()) if (!ifs.good())
throw std::runtime_error("Failed to read world.mt"); throw std::runtime_error("Failed to read world.mt");
std::string connect_string = read_setting("pgsql_connection", ifs); std::string connect_string = read_setting("pgsql_connection", ifs);
ifs.close(); ifs.close();
@ -27,11 +27,21 @@ DBPostgreSQL::DBPostgreSQL(const std::string &mapdir)
prepareStatement( prepareStatement(
"get_block_pos", "get_block_pos",
"SELECT posX, posY, posZ FROM blocks" "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( prepareStatement(
"get_blocks_z", "get_blocks",
"SELECT posX, posY, data FROM blocks WHERE posZ = $1::int4" "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, "START TRANSACTION;"));
@ -43,25 +53,38 @@ DBPostgreSQL::~DBPostgreSQL()
{ {
try { try {
checkResults(PQexec(db, "COMMIT;")); checkResults(PQexec(db, "COMMIT;"));
} catch (std::exception& caught) { } catch (const std::exception& caught) {
std::cerr << "could not finalize: " << caught.what() << std::endl; std::cerr << "could not finalize: " << caught.what() << std::endl;
} }
PQfinish(db); PQfinish(db);
} }
std::vector<BlockPos> DBPostgreSQL::getBlockPos()
std::vector<BlockPos> DBPostgreSQL::getBlockPos(BlockPos min, BlockPos max)
{ {
std::vector<BlockPos> positions; 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( PGresult *results = execPrepared(
"get_block_pos", 0, "get_block_pos", ARRLEN(args), args,
NULL, NULL, NULL, false, false argLen, argFmt, false
); );
int numrows = PQntuples(results); int numrows = PQntuples(results);
std::vector<BlockPos> positions;
positions.reserve(numrows);
for (int row = 0; row < numrows; ++row) for (int row = 0; row < numrows; ++row)
positions.push_back(pg_to_blockpos(results, row, 0)); positions.emplace_back(pg_to_blockpos(results, row, 0));
PQclear(results); PQclear(results);
@ -69,16 +92,20 @@ std::vector<BlockPos> DBPostgreSQL::getBlockPos()
} }
void DBPostgreSQL::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) 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 z = htonl(zPos);
int32_t const y1 = htonl(min_y);
int32_t const y2 = htonl(max_y - 1);
const void *args[] = { &z }; const void *args[] = { &x, &z, &y1, &y2 };
const int argLen[] = { sizeof(z) }; const int argLen[] = { 4, 4, 4, 4 };
const int argFmt[] = { 1 }; const int argFmt[] = { 1, 1, 1, 1 };
PGresult *results = execPrepared( PGresult *results = execPrepared(
"get_blocks_z", ARRLEN(args), args, "get_blocks", ARRLEN(args), args,
argLen, argFmt, false argLen, argFmt, false
); );
@ -86,24 +113,60 @@ void DBPostgreSQL::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zP
for (int row = 0; row < numrows; ++row) { for (int row = 0; row < numrows; ++row) {
BlockPos position; BlockPos position;
position.x = pg_binary_to_int(results, row, 0); position.x = xPos;
position.y = pg_binary_to_int(results, row, 1); position.y = pg_binary_to_int(results, row, 0);
position.z = zPos; position.z = zPos;
Block const b( blocks.emplace_back(
position, position,
ustring( ustring(
reinterpret_cast<unsigned char*>( reinterpret_cast<unsigned char*>(
PQgetvalue(results, row, 2) PQgetvalue(results, row, 1)
), ),
PQgetlength(results, row, 2) PQgetlength(results, row, 1)
) )
); );
blocks[position.x].push_back(b);
} }
PQclear(results); 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) PGresult *DBPostgreSQL::checkResults(PGresult *res, bool clear)
{ {
ExecStatusType statusType = PQresultStatus(res); ExecStatusType statusType = PQresultStatus(res);
@ -138,20 +201,15 @@ PGresult *DBPostgreSQL::execPrepared(
const char *stmtName, const int paramsNumber, const char *stmtName, const int paramsNumber,
const void **params, const void **params,
const int *paramsLengths, const int *paramsFormats, const int *paramsLengths, const int *paramsFormats,
bool clear, bool nobinary bool clear
) )
{ {
return checkResults(PQexecPrepared(db, stmtName, paramsNumber, return checkResults(PQexecPrepared(db, stmtName, paramsNumber,
(const char* const*) params, paramsLengths, paramsFormats, (const char* const*) params, paramsLengths, paramsFormats,
nobinary ? 1 : 0), clear 1 /* binary output */), clear
); );
} }
int DBPostgreSQL::pg_to_int(PGresult *res, int row, int col)
{
return atoi(PQgetvalue(res, row, col));
}
int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col) int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col)
{ {
int32_t* raw = reinterpret_cast<int32_t*>(PQgetvalue(res, row, col)); int32_t* raw = reinterpret_cast<int32_t*>(PQgetvalue(res, row, col));
@ -161,8 +219,8 @@ int DBPostgreSQL::pg_binary_to_int(PGresult *res, int row, int col)
BlockPos DBPostgreSQL::pg_to_blockpos(PGresult *res, int row, int col) BlockPos DBPostgreSQL::pg_to_blockpos(PGresult *res, int row, int col)
{ {
BlockPos result; BlockPos result;
result.x = pg_to_int(res, row, col); result.x = pg_binary_to_int(res, row, col);
result.y = pg_to_int(res, row, col + 1); result.y = pg_binary_to_int(res, row, col + 1);
result.z = pg_to_int(res, row, col + 2); result.z = pg_binary_to_int(res, row, col + 2);
return result; return result;
} }

View File

@ -20,7 +20,6 @@ static inline int64_t stoi64(const std::string &s)
return t; return t;
} }
static inline std::string i64tos(int64_t i) static inline std::string i64tos(int64_t i)
{ {
std::ostringstream os; std::ostringstream os;
@ -28,10 +27,11 @@ static inline std::string i64tos(int64_t i)
return os.str(); return os.str();
} }
DBRedis::DBRedis(const std::string &mapdir) DBRedis::DBRedis(const std::string &mapdir)
{ {
std::ifstream ifs((mapdir + "/world.mt").c_str()); std::ifstream ifs(mapdir + "world.mt");
if(!ifs.good()) if (!ifs.good())
throw std::runtime_error("Failed to read world.mt"); throw std::runtime_error("Failed to read world.mt");
std::string tmp; std::string tmp;
@ -40,17 +40,24 @@ DBRedis::DBRedis(const std::string &mapdir)
hash = read_setting("redis_hash", ifs); hash = read_setting("redis_hash", ifs);
ifs.seekg(0); ifs.seekg(0);
const char *addr = tmp.c_str(); if (tmp.find('/') != std::string::npos) {
int port = stoi64(read_setting_default("redis_port", ifs, "6379")); ctx = redisConnectUnix(tmp.c_str());
ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port); } else {
if(!ctx) { int port = stoi64(read_setting_default("redis_port", ifs, "6379"));
ctx = redisConnect(tmp.c_str(), port);
}
if (!ctx) {
throw std::runtime_error("Cannot allocate redis context"); 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; std::string err = std::string("Connection error: ") + ctx->errstr;
redisFree(ctx); redisFree(ctx);
throw std::runtime_error(err); 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(); loadPosCache();
} }
@ -61,14 +68,27 @@ DBRedis::~DBRedis()
} }
std::vector<BlockPos> DBRedis::getBlockPos() std::vector<BlockPos> DBRedis::getBlockPos(BlockPos min, BlockPos max)
{ {
return posCache; 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;
} }
std::string DBRedis::replyTypeStr(int type) { const char *DBRedis::replyTypeStr(int type)
switch(type) { {
switch (type) {
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
return "REDIS_REPLY_STATUS"; return "REDIS_REPLY_STATUS";
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
@ -82,7 +102,7 @@ std::string DBRedis::replyTypeStr(int type) {
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
return "REDIS_REPLY_ARRAY"; return "REDIS_REPLY_ARRAY";
default: default:
return "unknown"; return "(unknown)";
} }
} }
@ -91,89 +111,96 @@ void DBRedis::loadPosCache()
{ {
redisReply *reply; redisReply *reply;
reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str()); reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str());
if(!reply) if (!reply)
throw std::runtime_error("Redis command HKEYS failed"); throw std::runtime_error("Redis command HKEYS failed");
if(reply->type != REDIS_REPLY_ARRAY) if (reply->type != REDIS_REPLY_ARRAY)
REPLY_TYPE_ERR(reply, "HKEYS reply"); REPLY_TYPE_ERR(reply, "HKEYS reply");
for(size_t i = 0; i < reply->elements; i++) { for (size_t i = 0; i < reply->elements; i++) {
if(reply->element[i]->type != REDIS_REPLY_STRING) if (reply->element[i]->type != REDIS_REPLY_STRING)
REPLY_TYPE_ERR(reply->element[i], "HKEYS subreply"); REPLY_TYPE_ERR(reply->element[i], "HKEYS subreply");
posCache.push_back(decodeBlockPos(stoi64(reply->element[i]->str))); BlockPos pos = decodeBlockPos(stoi64(reply->element[i]->str));
posCache[pos.z].emplace_back(pos.x, pos.y);
} }
freeReplyObject(reply); freeReplyObject(reply);
} }
void DBRedis::HMGET(const std::vector<BlockPos> &positions, std::vector<ustring> *result) void DBRedis::HMGET(const std::vector<BlockPos> &positions,
std::function<void(std::size_t, ustring)> result)
{ {
const char *argv[DB_REDIS_HMGET_NUMFIELDS + 2]; const char *argv[DB_REDIS_HMGET_NUMFIELDS + 2];
argv[0] = "HMGET"; argv[0] = "HMGET";
argv[1] = hash.c_str(); argv[1] = hash.c_str();
std::vector<BlockPos>::const_iterator position = positions.begin(); auto position = positions.begin();
std::size_t remaining = positions.size(); size_t remaining = positions.size();
size_t abs_i = 0;
while (remaining > 0) { while (remaining > 0) {
const std::size_t batch_size = const size_t batch_size = mymin<size_t>(DB_REDIS_HMGET_NUMFIELDS, remaining);
(remaining > DB_REDIS_HMGET_NUMFIELDS) ? DB_REDIS_HMGET_NUMFIELDS : remaining;
redisReply *reply; redisReply *reply;
{ {
// storage to preserve validity of .c_str() // storage to preserve validity of .c_str()
std::string keys[batch_size]; std::string keys[batch_size];
for (std::size_t i = 0; i < batch_size; ++i) { for (size_t i = 0; i < batch_size; ++i) {
keys[i] = i64tos(encodeBlockPos(*position++)); keys[i] = i64tos(encodeBlockPos(*position++));
argv[i+2] = keys[i].c_str(); argv[i+2] = keys[i].c_str();
} }
reply = (redisReply*) redisCommandArgv(ctx, batch_size + 2, argv, NULL); reply = (redisReply*) redisCommandArgv(ctx, batch_size + 2, argv, NULL);
} }
if(!reply) if (!reply)
throw std::runtime_error("Redis command HMGET failed"); throw std::runtime_error("Redis command HMGET failed");
if (reply->type != REDIS_REPLY_ARRAY) { if (reply->type != REDIS_REPLY_ARRAY)
freeReplyObject(reply); REPLY_TYPE_ERR(reply, "HMGET reply");
REPLY_TYPE_ERR(reply, "HKEYS subreply");
}
if (reply->elements != batch_size) { if (reply->elements != batch_size) {
freeReplyObject(reply); freeReplyObject(reply);
throw std::runtime_error("HMGET wrong number of elements"); throw std::runtime_error("HMGET wrong number of elements");
} }
for (std::size_t i = 0; i < batch_size; ++i) { for (size_t i = 0; i < reply->elements; ++i) {
redisReply *subreply = reply->element[i]; redisReply *subreply = reply->element[i];
if(!subreply) if (subreply->type == REDIS_REPLY_NIL)
throw std::runtime_error("Redis command HMGET failed"); continue;
if (subreply->type != REDIS_REPLY_STRING) { else if (subreply->type != REDIS_REPLY_STRING)
freeReplyObject(reply); REPLY_TYPE_ERR(subreply, "HMGET subreply");
REPLY_TYPE_ERR(reply, "HKEYS subreply"); if (subreply->len == 0)
}
if (subreply->len == 0) {
freeReplyObject(reply);
throw std::runtime_error("HMGET empty string"); throw std::runtime_error("HMGET empty string");
} result(abs_i + i, ustring(
result->push_back(ustring((const unsigned char *) subreply->str, subreply->len)); reinterpret_cast<const unsigned char*>(subreply->str),
subreply->len
));
} }
freeReplyObject(reply); freeReplyObject(reply);
abs_i += batch_size;
remaining -= batch_size; remaining -= batch_size;
} }
} }
void DBRedis::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) void DBRedis::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
int16_t min_y, int16_t max_y)
{ {
std::vector<BlockPos> z_positions; auto it = posCache.find(z);
for (std::vector<BlockPos>::const_iterator it = posCache.begin(); it != posCache.end(); ++it) { if (it == posCache.cend())
if (it->z != zPos) { return;
continue;
}
z_positions.push_back(*it);
}
std::vector<ustring> z_blocks;
HMGET(z_positions, &z_blocks);
std::vector<ustring>::const_iterator z_block = z_blocks.begin(); std::vector<BlockPos> positions;
for (std::vector<BlockPos>::const_iterator pos = z_positions.begin(); for (auto pos2 : it->second) {
pos != z_positions.end(); if (pos2.first == x && pos2.second >= min_y && pos2.second < max_y)
++pos, ++z_block) { positions.emplace_back(x, pos2.second, z);
blocks[pos->x].push_back(Block(*pos, *z_block));
} }
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);
} }

View File

@ -1,6 +1,8 @@
#include <stdexcept> #include <stdexcept>
#include <unistd.h> // for usleep #include <unistd.h> // for usleep
#include <iostream> #include <iostream>
#include <algorithm>
#include <time.h>
#include "db-sqlite3.h" #include "db-sqlite3.h"
#include "types.h" #include "types.h"
@ -11,7 +13,6 @@
} }
#define SQLOK(f) SQLRES(f, SQLITE_OK) #define SQLOK(f) SQLRES(f, SQLITE_OK)
DBSQLite3::DBSQLite3(const std::string &mapdir) DBSQLite3::DBSQLite3(const std::string &mapdir)
{ {
int result; int result;
@ -24,9 +25,17 @@ DBSQLite3::DBSQLite3(const std::string &mapdir)
"SELECT pos, data FROM blocks WHERE pos BETWEEN ? AND ?", "SELECT pos, data FROM blocks WHERE pos BETWEEN ? AND ?",
-1, &stmt_get_blocks_z, NULL)) -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, SQLOK(prepare_v2(db,
"SELECT pos FROM blocks", "SELECT pos FROM blocks",
-1, &stmt_get_block_pos, NULL)) -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))
} }
@ -34,59 +43,154 @@ DBSQLite3::~DBSQLite3()
{ {
sqlite3_finalize(stmt_get_blocks_z); sqlite3_finalize(stmt_get_blocks_z);
sqlite3_finalize(stmt_get_block_pos); 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) { if (sqlite3_close(db) != SQLITE_OK) {
std::cerr << "Error closing SQLite database." << std::endl; std::cerr << "Error closing SQLite database." << std::endl;
}; };
} }
std::vector<BlockPos> DBSQLite3::getBlockPos()
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)
{ {
int result; 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; std::vector<BlockPos> positions;
while ((result = sqlite3_step(stmt_get_block_pos)) != SQLITE_DONE) { while ((result = sqlite3_step(stmt)) != SQLITE_DONE) {
if (result == SQLITE_ROW) { if (result == SQLITE_BUSY) { // Wait some time and try again
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); usleep(10000);
} else { } else if (result != SQLITE_ROW) {
throw std::runtime_error(sqlite3_errmsg(db)); 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_get_block_pos)); SQLOK(reset(stmt));
return positions; return positions;
} }
void DBSQLite3::getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) void DBSQLite3::loadBlockCache(int16_t zPos)
{ {
int result; int result;
blockCache.clear();
// Magic numbers! int64_t minPos, maxPos;
int64_t minPos = encodeBlockPos(BlockPos(0, -2048, zPos)); getPosRange(minPos, maxPos, zPos, 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, 1, minPos));
SQLOK(bind_int64(stmt_get_blocks_z, 2, maxPos)); SQLOK(bind_int64(stmt_get_blocks_z, 2, maxPos));
while ((result = sqlite3_step(stmt_get_blocks_z)) != SQLITE_DONE) { while ((result = sqlite3_step(stmt_get_blocks_z)) != SQLITE_DONE) {
if (result == SQLITE_ROW) { if (result == SQLITE_BUSY) { // Wait some time and try again
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_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); usleep(10000);
} else { } else if (result != SQLITE_ROW) {
throw std::runtime_error(sqlite3_errmsg(db)); 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)); SQLOK(reset(stmt_get_blocks_z))
} }
#undef SQLRES
#undef SQLOK
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::cerr << "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 {
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));
}
SQLOK(reset(stmt_get_block_exact))
}
}

View File

@ -1,13 +1,9 @@
#ifndef BLOCKDECODER_H #pragma once
#define BLOCKDECODER_H
#if __cplusplus >= 201103L #include <cstdint>
#include <unordered_map> #include <unordered_map>
#else
#include <map>
#endif
#include "types.h" #include "types.h"
#include <ZstdDecompressor.h>
class BlockDecoder { class BlockDecoder {
public: public:
@ -16,20 +12,17 @@ public:
void reset(); void reset();
void decode(const ustring &data); void decode(const ustring &data);
bool isEmpty() const; bool isEmpty() const;
std::string getNode(u8 x, u8 y, u8 z) const; // returns "" for air, ignore and invalid nodes // returns "" for air, ignore and invalid nodes
const std::string &getNode(u8 x, u8 y, u8 z) const;
private: private:
#if __cplusplus >= 201103L typedef std::unordered_map<uint16_t, std::string> NameMap;
typedef std::unordered_map<int, std::string> NameMap;
#else
typedef std::map<int, std::string> NameMap;
#endif
NameMap m_nameMap; NameMap m_nameMap;
int m_blockAirId; uint16_t m_blockAirId, m_blockIgnoreId;
int m_blockIgnoreId;
u8 m_version; u8 m_version, m_contentWidth;
ustring m_mapData; ustring m_mapData;
};
#endif // BLOCKDECODER_H // one instance for performance
ZstdDecompressor m_zstd_decompressor;
};

View File

@ -1,5 +1,4 @@
#ifndef IMAGE_HEADER #pragma once
#define IMAGE_HEADER
#include "types.h" #include "types.h"
#include <string> #include <string>
@ -9,7 +8,6 @@ struct Color {
Color() : r(0), g(0), b(0), a(0) {}; 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) : 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) {}; 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; u8 r, g, b, a;
}; };
@ -19,6 +17,9 @@ public:
Image(int width, int height); Image(int width, int height);
~Image(); ~Image();
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
void setPixel(int x, int y, const Color &c); void setPixel(int x, int y, const Color &c);
Color getPixel(int x, int y); Color getPixel(int x, int y);
void drawLine(int x1, int y1, int x2, int y2, const Color &c); void drawLine(int x1, int y1, int x2, int y2, const Color &c);
@ -28,10 +29,6 @@ public:
void save(const std::string &filename); void save(const std::string &filename);
private: private:
Image(const Image&);
int m_width, m_height; int m_width, m_height;
gdImagePtr m_image; gdImagePtr m_image;
}; };
#endif // IMAGE_HEADER

View File

@ -1,25 +1,18 @@
/* #pragma once
* =====================================================================
* Version: 1.0
* Created: 25.08.2012 10:55:29
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#ifndef PIXELATTRIBUTES_H_ADZ35GYF #include <climits>
#define PIXELATTRIBUTES_H_ADZ35GYF #include <cstdint>
#include <limits> #define BLOCK_SIZE 16
#include <stdint.h>
#include "config.h"
struct PixelAttribute { struct PixelAttribute {
PixelAttribute(): height(std::numeric_limits<int>::min()), thickness(0) {}; PixelAttribute() : height(INT16_MIN), thickness(0) {};
int height;
int16_t height;
uint8_t thickness; uint8_t thickness;
inline bool valid_height() {
return height != std::numeric_limits<int>::min(); inline bool valid_height() const {
return height != INT16_MIN;
} }
}; };
@ -28,9 +21,13 @@ class PixelAttributes
public: public:
PixelAttributes(); PixelAttributes();
virtual ~PixelAttributes(); virtual ~PixelAttributes();
void setWidth(int width); void setWidth(int width);
void scroll(); void scroll();
inline PixelAttribute &attribute(int z, int x) { return m_pixelAttributes[z + 1][x + 1]; };
inline PixelAttribute &attribute(int z, int x) {
return m_pixelAttributes[z + 1][x + 1];
};
private: private:
void freeAttributes(); void freeAttributes();
@ -45,6 +42,3 @@ private:
PixelAttribute *m_pixelAttributes[BLOCK_SIZE + 2]; // 1px gradient + empty PixelAttribute *m_pixelAttributes[BLOCK_SIZE + 2]; // 1px gradient + empty
int m_width; int m_width;
}; };
#endif /* end of include guard: PIXELATTRIBUTES_H_ADZ35GYF */

View File

@ -1,5 +1,4 @@
#ifndef PLAYERATTRIBUTES_H_D7THWFVV #pragma once
#define PLAYERATTRIBUTES_H_D7THWFVV
#include <list> #include <list>
#include <string> #include <string>
@ -7,7 +6,7 @@
struct Player struct Player
{ {
std::string name; std::string name;
double x, y, z; float x, y, z;
}; };
class PlayerAttributes class PlayerAttributes
@ -16,8 +15,8 @@ public:
typedef std::list<Player> Players; typedef std::list<Player> Players;
PlayerAttributes(const std::string &worldDir); PlayerAttributes(const std::string &worldDir);
Players::iterator begin(); Players::const_iterator begin() const;
Players::iterator end(); Players::const_iterator end() const;
private: private:
void readFiles(const std::string &playersPath); void readFiles(const std::string &playersPath);
@ -25,6 +24,3 @@ private:
Players m_players; Players m_players;
}; };
#endif /* end of include guard: PLAYERATTRIBUTES_H_D7THWFVV */

View File

@ -1,25 +1,20 @@
#ifndef TILEGENERATOR_HEADER #pragma once
#define TILEGENERATOR_HEADER
#include <iosfwd> #include <iostream>
#include <list>
#include <config.h>
#if __cplusplus >= 201103L
#include <unordered_map>
#include <unordered_set>
#else
#include <map> #include <map>
#include <set> #include <set>
#endif #include <unordered_map>
#include <stdint.h> #include <cstdint>
#include <string> #include <string>
#include "PixelAttributes.h" #include "PixelAttributes.h"
#include "BlockDecoder.h"
#include "Image.h" #include "Image.h"
#include "db.h" #include "db.h"
#include "types.h" #include "types.h"
class BlockDecoder;
class Image;
enum { enum {
SCALE_TOP = (1 << 0), SCALE_TOP = (1 << 0),
SCALE_BOTTOM = (1 << 1), SCALE_BOTTOM = (1 << 1),
@ -27,11 +22,20 @@ enum {
SCALE_RIGHT = (1 << 3), 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 { struct ColorEntry {
ColorEntry(): r(0), g(0), b(0), a(0), t(0) {}; 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) {}; ColorEntry(uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t t) :
inline Color to_color() const { return Color(r, g, b, a); } r(r), g(g), b(b), a(a), t(t) {};
uint8_t r, g, b, a, t; inline Color toColor() const { return Color(r, g, b, a); }
uint8_t r, g, b, a; // Red, Green, Blue, Alpha
uint8_t t; // "thickness" value
}; };
struct BitmapThing { // 16x16 bitmap struct BitmapThing { // 16x16 bitmap
@ -39,17 +43,19 @@ struct BitmapThing { // 16x16 bitmap
for (int i = 0; i < 16; ++i) for (int i = 0; i < 16; ++i)
val[i] = 0; val[i] = 0;
} }
inline bool full() const { inline bool any_neq(uint16_t v) const {
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
if (val[i] != 0xffff) if (val[i] != v)
return false; return true;
} }
return true; return false;
} }
inline bool any() const { return any_neq(0); }
inline bool full() const { return !any_neq(0xffff); }
inline void set(unsigned int x, unsigned int z) { inline void set(unsigned int x, unsigned int z) {
val[z] |= (1 << x); val[z] |= (1 << x);
} }
inline bool get(unsigned int x, unsigned int z) { inline bool get(unsigned int x, unsigned int z) const {
return !!(val[z] & (1 << x)); return !!(val[z] & (1 << x));
} }
@ -60,13 +66,7 @@ struct BitmapThing { // 16x16 bitmap
class TileGenerator class TileGenerator
{ {
private: private:
#if __cplusplus >= 201103L
typedef std::unordered_map<std::string, ColorEntry> ColorMap; typedef std::unordered_map<std::string, ColorEntry> ColorMap;
typedef std::unordered_set<std::string> NameSet;
#else
typedef std::map<std::string, ColorEntry> ColorMap;
typedef std::set<std::string> NameSet;
#endif
public: public:
TileGenerator(); TileGenerator();
@ -74,7 +74,7 @@ public:
void setBgColor(const std::string &bgColor); void setBgColor(const std::string &bgColor);
void setScaleColor(const std::string &scaleColor); void setScaleColor(const std::string &scaleColor);
void setOriginColor(const std::string &originColor); void setOriginColor(const std::string &originColor);
void setPlayerColor(const std::string &playerColor); Color parseColor(const std::string &color); void setPlayerColor(const std::string &playerColor);
void setDrawOrigin(bool drawOrigin); void setDrawOrigin(bool drawOrigin);
void setDrawPlayers(bool drawPlayers); void setDrawPlayers(bool drawPlayers);
void setDrawScale(bool drawScale); void setDrawScale(bool drawScale);
@ -83,14 +83,19 @@ public:
void setGeometry(int x, int y, int w, int h); void setGeometry(int x, int y, int w, int h);
void setMinY(int y); void setMinY(int y);
void setMaxY(int y); void setMaxY(int y);
void setExhaustiveSearch(int mode);
void parseColorsFile(const std::string &fileName); void parseColorsFile(const std::string &fileName);
void setBackend(std::string backend); void setBackend(std::string backend);
void generate(const std::string &input, const std::string &output);
void printGeometry(const std::string &input);
void setZoom(int zoom); void setZoom(int zoom);
void setScales(uint flags); void setScales(uint flags);
void setDontWriteEmpty(bool f); void setDontWriteEmpty(bool f);
void generate(const std::string &input, const std::string &output);
void printGeometry(const std::string &input);
void dumpBlock(const std::string &input, BlockPos pos);
static std::set<std::string> getSupportedBackends();
private: private:
void parseColorsStream(std::istream &in); void parseColorsStream(std::istream &in);
void openDb(const std::string &input); void openDb(const std::string &input);
@ -98,7 +103,6 @@ private:
void loadBlocks(); void loadBlocks();
void createImage(); void createImage();
void renderMap(); void renderMap();
std::list<int> getZValueList() const;
void renderMapBlock(const BlockDecoder &blk, const BlockPos &pos); void renderMapBlock(const BlockDecoder &blk, const BlockPos &pos);
void renderMapBlockBottom(const BlockPos &pos); void renderMapBlockBottom(const BlockPos &pos);
void renderShading(int zPos); void renderShading(int zPos);
@ -107,6 +111,7 @@ private:
void renderPlayers(const std::string &inputPath); void renderPlayers(const std::string &inputPath);
void writeImage(const std::string &output); void writeImage(const std::string &output);
void printUnknown(); void printUnknown();
void reportProgress(size_t count);
int getImageX(int val, bool absolute=false) const; int getImageX(int val, bool absolute=false) const;
int getImageY(int val, bool absolute=false) const; int getImageY(int val, bool absolute=false) const;
void setZoomed(int x, int y, Color color); void setZoomed(int x, int y, Color color);
@ -128,28 +133,35 @@ private:
DB *m_db; DB *m_db;
Image *m_image; Image *m_image;
PixelAttributes m_blockPixelAttributes; PixelAttributes m_blockPixelAttributes;
/* smallest/largest seen X or Z block coordinate */
int m_xMin; int m_xMin;
int m_xMax; int m_xMax;
int m_zMin; int m_zMin;
int m_zMax; int m_zMax;
/* Y limits for rendered area (node units) */
int m_yMin; int m_yMin;
int m_yMax; int m_yMax;
int m_geomX; /* limits for rendered area (block units) */
int m_geomY; int16_t m_geomX;
int m_geomX2; int16_t m_geomY; /* Y in terms of rendered image, Z in the world */
int m_geomY2; int16_t m_geomX2;
int16_t m_geomY2;
int m_mapWidth; int m_mapWidth;
int m_mapHeight; int m_mapHeight;
std::list<std::pair<int, int> > m_positions; int m_exhaustiveSearch;
std::set<std::string> m_unknownNodes;
bool m_renderedAny;
std::map<int16_t, std::set<int16_t>> m_positions; /* indexed by Z, contains X coords */
ColorMap m_colorMap; ColorMap m_colorMap;
BitmapThing m_readPixels; BitmapThing m_readPixels;
BitmapThing m_readInfo; BitmapThing m_readInfo;
NameSet m_unknownNodes;
Color m_color[16][16]; Color m_color[16][16];
uint8_t m_thickness[16][16]; uint8_t m_thickness[16][16];
int m_zoom; int m_zoom;
uint m_scales; uint m_scales;
}; // class TileGenerator
#endif // TILEGENERATOR_HEADER size_t m_progressMax;
int m_progressLast; // percentage
}; // class TileGenerator

View File

@ -1,37 +1,20 @@
/* #pragma once
* =====================================================================
* Version: 1.0
* Created: 18.09.2012 10:20:51
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#ifndef ZLIBDECOMPRESSOR_H_ZQL1PN8Q #include <exception>
#define ZLIBDECOMPRESSOR_H_ZQL1PN8Q
#include <cstdlib>
#include <string>
#include "types.h" #include "types.h"
class ZlibDecompressor class ZlibDecompressor
{ {
public: public:
class DecompressError { class DecompressError : std::exception {};
};
ZlibDecompressor(const unsigned char *data, std::size_t size); ZlibDecompressor(const u8 *data, size_t size);
~ZlibDecompressor(); ~ZlibDecompressor();
void setSeekPos(std::size_t seekPos); void setSeekPos(size_t seekPos);
std::size_t seekPos() const; size_t seekPos() const;
ustring decompress(); ustring decompress();
private: private:
const unsigned char *m_data; const u8 *m_data;
std::size_t m_seekPos; size_t m_seekPos, m_size;
std::size_t m_size; };
}; /* ----- end of class ZlibDecompressor ----- */
#endif /* end of include guard: ZLIBDECOMPRESSOR_H_ZQL1PN8Q */

View File

@ -0,0 +1,21 @@
#pragma once
#include <exception>
#include "types.h"
class ZstdDecompressor
{
public:
class DecompressError : std::exception {};
ZstdDecompressor();
~ZstdDecompressor();
void setData(const u8 *data, size_t size, size_t seekPos);
size_t seekPos() const;
ustring decompress();
private:
void *m_stream; // ZSTD_DStream
const u8 *m_data;
size_t m_seekPos, m_size;
};

View File

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

View File

@ -4,8 +4,6 @@
#define PATH_SEPARATOR '/' #define PATH_SEPARATOR '/'
#endif #endif
#define BLOCK_SIZE 16
#ifdef USE_CMAKE_CONFIG_H #ifdef USE_CMAKE_CONFIG_H
#include "cmake_config.h" #include "cmake_config.h"
#else #else

View File

@ -1,21 +1,28 @@
#ifndef DB_LEVELDB_HEADER #pragma once
#define DB_LEVELDB_HEADER
#include "db.h" #include "db.h"
#include <unordered_map>
#include <utility>
#include <leveldb/db.h> #include <leveldb/db.h>
class DBLevelDB : public DB { class DBLevelDB : public DB {
public: public:
DBLevelDB(const std::string &mapdir); DBLevelDB(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos(); std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos); void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
virtual ~DBLevelDB(); 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: private:
using pos2d = std::pair<int16_t, int16_t>;
void loadPosCache(); void loadPosCache();
std::vector<BlockPos> posCache; // indexed by Z, contains all (x,y) position pairs
std::unordered_map<int16_t, std::vector<pos2d>> posCache;
leveldb::DB *db; leveldb::DB *db;
}; };
#endif // DB_LEVELDB_HEADER

View File

@ -1,5 +1,4 @@
#ifndef _DB_POSTGRESQL_H #pragma once
#define _DB_POSTGRESQL_H
#include "db.h" #include "db.h"
#include <libpq-fe.h> #include <libpq-fe.h>
@ -7,23 +6,27 @@
class DBPostgreSQL : public DB { class DBPostgreSQL : public DB {
public: public:
DBPostgreSQL(const std::string &mapdir); DBPostgreSQL(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos(); std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos); void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
virtual ~DBPostgreSQL(); 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: protected:
PGresult *checkResults(PGresult *res, bool clear = true); PGresult *checkResults(PGresult *res, bool clear = true);
void prepareStatement(const std::string &name, const std::string &sql); void prepareStatement(const std::string &name, const std::string &sql);
PGresult *execPrepared( PGresult *execPrepared(
const char *stmtName, const int paramsNumber, const char *stmtName, const int paramsNumber,
const void **params, const void **params,
const int *paramsLengths = NULL, const int *paramsFormats = NULL, const int *paramsLengths = nullptr, const int *paramsFormats = nullptr,
bool clear = true, bool nobinary = true bool clear = true
); );
int pg_to_int(PGresult *res, int row, int col);
int pg_binary_to_int(PGresult *res, int row, int col); int pg_binary_to_int(PGresult *res, int row, int col);
BlockPos pg_to_blockpos(PGresult *res, int row, int col); BlockPos pg_to_blockpos(PGresult *res, int row, int col);
private: private:
PGconn *db; PGconn *db;
}; };
#endif // _DB_POSTGRESQL_H

View File

@ -1,25 +1,34 @@
#ifndef DB_REDIS_HEADER #pragma once
#define DB_REDIS_HEADER
#include "db.h" #include "db.h"
#include <hiredis.h> #include <unordered_map>
#include <utility>
#include <functional>
#include <hiredis/hiredis.h>
class DBRedis : public DB { class DBRedis : public DB {
public: public:
DBRedis(const std::string &mapdir); DBRedis(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos(); std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos); void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
virtual ~DBRedis(); 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: private:
static std::string replyTypeStr(int type); using pos2d = std::pair<int16_t, int16_t>;
static const char *replyTypeStr(int type);
void loadPosCache(); void loadPosCache();
void HMGET(const std::vector<BlockPos> &positions, std::vector<ustring> *result); void HMGET(const std::vector<BlockPos> &positions,
std::function<void(std::size_t, ustring)> result);
std::vector<BlockPos> posCache; // indexed by Z, contains all (x,y) position pairs
std::unordered_map<int16_t, std::vector<pos2d>> posCache;
redisContext *ctx; redisContext *ctx;
std::string hash; std::string hash;
}; };
#endif // DB_REDIS_HEADER

View File

@ -1,20 +1,33 @@
#ifndef _DB_SQLITE3_H #pragma once
#define _DB_SQLITE3_H
#include "db.h" #include "db.h"
#include <unordered_map>
#include <sqlite3.h> #include <sqlite3.h>
class DBSQLite3 : public DB { class DBSQLite3 : public DB {
public: public:
DBSQLite3(const std::string &mapdir); DBSQLite3(const std::string &mapdir);
virtual std::vector<BlockPos> getBlockPos(); std::vector<BlockPos> getBlockPos(BlockPos min, BlockPos max) override;
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos); void getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
virtual ~DBSQLite3(); 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: 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 *db;
sqlite3_stmt *stmt_get_block_pos; sqlite3_stmt *stmt_get_block_pos;
sqlite3_stmt *stmt_get_block_pos_z;
sqlite3_stmt *stmt_get_blocks_z; sqlite3_stmt *stmt_get_blocks_z;
}; sqlite3_stmt *stmt_get_block_exact;
#endif // _DB_SQLITE3_H int16_t blockCachedZ = -10000;
std::unordered_map<int16_t, BlockList> blockCache; // indexed by X
};

View File

@ -1,23 +1,22 @@
#ifndef DB_HEADER #pragma once
#define DB_HEADER
#include <stdint.h> #include <cstdint>
#include <map>
#include <list> #include <list>
#include <vector> #include <vector>
#include <string>
#include <utility> #include <utility>
#include "types.h" #include "types.h"
class BlockPos { struct BlockPos {
public:
int16_t x; int16_t x;
int16_t y; int16_t y;
int16_t z; int16_t z;
BlockPos() : x(0), y(0), z(0) {} BlockPos() : x(0), y(0), z(0) {}
explicit BlockPos(int16_t v) : x(v), y(v), z(v) {}
BlockPos(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {} 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 bool operator < (const BlockPos &p) const
{ {
if (z > p.z) if (z > p.z)
@ -43,13 +42,31 @@ typedef std::list<Block> BlockList;
class DB { class DB {
protected: protected:
// Helpers that implement the hashed positions used by most backends
inline int64_t encodeBlockPos(const BlockPos pos) const; inline int64_t encodeBlockPos(const BlockPos pos) const;
inline BlockPos decodeBlockPos(int64_t hash) const; inline BlockPos decodeBlockPos(int64_t hash) const;
public: public:
virtual std::vector<BlockPos> getBlockPos() = 0; /* Return all block positions inside the range given by min and max,
virtual void getBlocksOnZ(std::map<int16_t, BlockList> &blocks, int16_t zPos) = 0; * so that min.x <= x < max.x, ...
virtual ~DB() {}; */
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() {}
}; };
@ -104,4 +121,3 @@ inline BlockPos DB::decodeBlockPos(int64_t hash) const
* End black magic * * End black magic *
*******************/ *******************/
#endif // DB_HEADER

View File

@ -1,18 +1,21 @@
#ifndef UTIL_H #pragma once
#define UTIL_H
#include <string> #include <string>
#include <fstream> #include <iostream>
template<typename T>
static inline T mymax(T a, T b)
{
return (a > b) ? a : b;
}
template<typename T>
static inline T mymin(T a, T b)
{
return (a > b) ? b : a;
}
std::string read_setting(const std::string &name, std::istream &is); 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) std::string read_setting_default(const std::string &name, std::istream &is,
{ const std::string &def);
try {
return read_setting(name, is);
} catch(std::runtime_error &e) {
return def;
}
}
#endif // UTIL_H

View File

@ -1,63 +1,92 @@
#include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <getopt.h> #include <getopt.h>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <map> #include <utility>
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include "cmake_config.h" #include "config.h"
#include "TileGenerator.h" #include "TileGenerator.h"
void usage() static void usage()
{ {
const char *usage_text = "minetestmapper [options]\n" const std::pair<const char*, const char*> options[] = {
" -i/--input <world_path>\n" {"-i/--input", "<world_path>"},
" -o/--output <output_image.png>\n" {"-o/--output", "<output_image.png>"},
" --bgcolor <color>\n" {"--bgcolor", "<color>"},
" --scalecolor <color>\n" {"--scalecolor", "<color>"},
" --playercolor <color>\n" {"--playercolor", "<color>"},
" --origincolor <color>\n" {"--origincolor", "<color>"},
" --drawscale\n" {"--drawscale", ""},
" --drawplayers\n" {"--drawplayers", ""},
" --draworigin\n" {"--draworigin", ""},
" --drawalpha\n" {"--drawalpha", ""},
" --noshading\n" {"--noshading", ""},
" --noemptyimage\n" {"--noemptyimage", ""},
" --min-y <y>\n" {"--min-y", "<y>"},
" --max-y <y>\n" {"--max-y", "<y>"},
" --backend <backend>\n" {"--backend", "<backend>"},
" --geometry x:y+w+h\n" {"--geometry", "x:y+w+h"},
" --extent\n" {"--extent", ""},
" --zoom <zoomlevel>\n" {"--zoom", "<zoomlevel>"},
" --colors <colors.txt>\n" {"--colors", "<colors.txt>"},
" --scales [t][b][l][r]\n" {"--scales", "[t][b][l][r]"},
"Color format: '#000000'\n"; {"--exhaustive", "never|y|full|auto"},
std::cout << usage_text; {"--dumpblock", "x,y,z"},
};
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");
} }
bool file_exists(const std::string &path) static inline bool file_exists(const std::string &path)
{ {
std::ifstream ifs(path.c_str()); std::ifstream ifs(path);
return ifs.is_open(); return ifs.is_open();
} }
std::string search_colors(const std::string &worldpath) static inline int stoi(const char *s)
{ {
if(file_exists(worldpath + "/colors.txt")) std::istringstream iss(s);
int ret;
iss >> ret;
return ret;
}
static std::string search_colors(const std::string &worldpath)
{
if (file_exists(worldpath + "/colors.txt"))
return worldpath + "/colors.txt"; return worldpath + "/colors.txt";
#ifndef _WIN32 #ifndef _WIN32
char *home = std::getenv("HOME"); char *home = std::getenv("HOME");
if(home) { if (home) {
std::string check = ((std::string) home) + "/.minetest/colors.txt"; std::string check = std::string(home) + "/.minetest/colors.txt";
if(file_exists(check)) if (file_exists(check))
return check; return check;
} }
#endif #endif
if(!(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0') && file_exists(SHAREDIR "/colors.txt")) constexpr bool sharedir_valid = !(SHAREDIR[0] == '.' || SHAREDIR[0] == '\0');
if (sharedir_valid && file_exists(SHAREDIR "/colors.txt"))
return SHAREDIR "/colors.txt"; return SHAREDIR "/colors.txt";
std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl; std::cerr << "Warning: Falling back to using colors.txt from current directory." << std::endl;
@ -66,7 +95,7 @@ std::string search_colors(const std::string &worldpath)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
static struct option long_options[] = const static struct option long_options[] =
{ {
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"input", required_argument, 0, 'i'}, {"input", required_argument, 0, 'i'},
@ -89,15 +118,18 @@ int main(int argc, char *argv[])
{"colors", required_argument, 0, 'C'}, {"colors", required_argument, 0, 'C'},
{"scales", required_argument, 0, 'f'}, {"scales", required_argument, 0, 'f'},
{"noemptyimage", no_argument, 0, 'n'}, {"noemptyimage", no_argument, 0, 'n'},
{"exhaustive", required_argument, 0, 'j'},
{"dumpblock", required_argument, 0, 'k'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
std::string input; std::string input;
std::string output; std::string output;
std::string colors = ""; std::string colors;
bool onlyPrintExtent = false;
BlockPos dumpblock(INT16_MIN);
TileGenerator generator; TileGenerator generator;
bool onlyPrintExtent = false;
while (1) { while (1) {
int option_index; int option_index;
int c = getopt_long(argc, argv, "hi:o:", long_options, &option_index); int c = getopt_long(argc, argv, "hi:o:", long_options, &option_index);
@ -108,7 +140,6 @@ int main(int argc, char *argv[])
case 'h': case 'h':
usage(); usage();
return 0; return 0;
break;
case 'i': case 'i':
input = optarg; input = optarg;
break; break;
@ -148,19 +179,11 @@ int main(int argc, char *argv[])
case 'd': case 'd':
generator.setBackend(optarg); generator.setBackend(optarg);
break; break;
case 'a': { case 'a':
std::istringstream iss(optarg); generator.setMinY(stoi(optarg));
int miny;
iss >> miny;
generator.setMinY(miny);
}
break; break;
case 'c': { case 'c':
std::istringstream iss(optarg); generator.setMaxY(stoi(optarg));
int maxy;
iss >> maxy;
generator.setMaxY(maxy);
}
break; break;
case 'g': { case 'g': {
std::istringstream geometry(optarg); std::istringstream geometry(optarg);
@ -176,23 +199,19 @@ int main(int argc, char *argv[])
break; break;
case 'f': { case 'f': {
uint flags = 0; uint flags = 0;
if(strchr(optarg, 't') != NULL) if (strchr(optarg, 't'))
flags |= SCALE_TOP; flags |= SCALE_TOP;
if(strchr(optarg, 'b') != NULL) if (strchr(optarg, 'b'))
flags |= SCALE_BOTTOM; flags |= SCALE_BOTTOM;
if(strchr(optarg, 'l') != NULL) if (strchr(optarg, 'l'))
flags |= SCALE_LEFT; flags |= SCALE_LEFT;
if(strchr(optarg, 'r') != NULL) if (strchr(optarg, 'r'))
flags |= SCALE_RIGHT; flags |= SCALE_RIGHT;
generator.setScales(flags); generator.setScales(flags);
} }
break; break;
case 'z': { case 'z':
std::istringstream iss(optarg); generator.setZoom(stoi(optarg));
int zoom;
iss >> zoom;
generator.setZoom(zoom);
}
break; break;
case 'C': case 'C':
colors = optarg; colors = optarg;
@ -200,12 +219,34 @@ int main(int argc, char *argv[])
case 'n': case 'n':
generator.setDontWriteEmpty(true); generator.setDontWriteEmpty(true);
break; break;
case 'j': {
int mode = EXH_AUTO;;
if (!strcmp(optarg, "never"))
mode = EXH_NEVER;
else if (!strcmp(optarg, "y"))
mode = EXH_Y;
else if (!strcmp(optarg, "full"))
mode = EXH_FULL;
generator.setExhaustiveSearch(mode);
}
break;
case 'k': {
std::istringstream iss(optarg);
char c, c2;
iss >> dumpblock.x >> c >> dumpblock.y >> c2 >> dumpblock.z;
if (iss.fail() || c != ',' || c2 != ',') {
usage();
exit(1);
}
break;
}
default: default:
exit(1); exit(1);
} }
} }
if (input.empty() || (!onlyPrintExtent && output.empty())) { const bool need_output = !onlyPrintExtent && dumpblock.x == INT16_MIN;
if (input.empty() || (need_output && output.empty())) {
usage(); usage();
return 0; return 0;
} }
@ -215,14 +256,17 @@ int main(int argc, char *argv[])
if (onlyPrintExtent) { if (onlyPrintExtent) {
generator.printGeometry(input); generator.printGeometry(input);
return 0; return 0;
} else if (dumpblock.x != INT16_MIN) {
generator.dumpBlock(input, dumpblock);
return 0;
} }
if(colors == "") if(colors.empty())
colors = search_colors(input); colors = search_colors(input);
generator.parseColorsFile(colors); generator.parseColorsFile(colors);
generator.generate(input, output); generator.generate(input, output);
} catch(std::runtime_error &e) { } catch (const std::exception &e) {
std::cerr << "Exception: " << e.what() << std::endl; std::cerr << "Exception: " << e.what() << std::endl;
return 1; return 1;
} }

View File

@ -70,15 +70,15 @@ Don't draw nodes above this y value, e.g. "--max-y 75"
.TP .TP
.BR \-\-backend " " \fIbackend\fR .BR \-\-backend " " \fIbackend\fR
Use specific map backend; supported: *sqlite3*, *leveldb*, *redis*, *postgresql*, e.g. "--backend leveldb" Use specific map backend; supported: \fIsqlite3\fP, \fIleveldb\fP, \fIredis\fP, \fIpostgresql\fP, e.g. "--backend leveldb"
.TP .TP
.BR \-\-geometry " " \fIgeometry\fR .BR \-\-geometry " " \fIgeometry\fR
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" 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 .TP
.BR \-\-extent " " \fIextent\fR .BR \-\-extent
Dont render the image, just print the extent of the map that would be generated, in the same format as the geometry above. 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 .TP
.BR \-\-zoom " " \fIfactor\fR .BR \-\-zoom " " \fIfactor\fR
@ -90,7 +90,22 @@ Forcefully set path to colors.txt file (it's autodetected otherwise), e.g. "--co
.TP .TP
.BR \-\-scales " " \fIedges\fR .BR \-\-scales " " \fIedges\fR
Draw scales on specified image edges (letters *t b l r* meaning top, bottom, left and right), e.g. "--scales tbr" 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.
.TP
.BR \-\-dumpblock " " \fIpos\fR
Instead of rendering anything try to load the block at the given position (\fIx,y,z\fR) and print its raw data as hexadecimal.
.SH MORE INFORMATION .SH MORE INFORMATION
Website: https://github.com/minetest/minetestmapper Website: https://github.com/minetest/minetestmapper

View File

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

56
util/build-mingw.sh Executable file
View File

@ -0,0 +1,56 @@
#!/bin/bash -e
[ -z "$CXX" ] && exit 255
export CC=false # don't need it actually
variant=win32
[[ "$(basename "$CXX")" == "x86_64-"* ]] && variant=win64
#######
# this expects unpacked libraries similar to what Minetest's buildbot uses
# $extradlls will typically point to the DLLs for libgcc, libstdc++ and libpng
libgd_dir=
zlib_dir=
zstd_dir=
sqlite_dir=
leveldb_dir=
extradlls=()
#######
[ -f ./CMakeLists.txt ] || exit 1
cmake -S . -B build \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_EXE_LINKER_FLAGS="-s" \
\
-DENABLE_LEVELDB=1 \
\
-DLEVELDB_INCLUDE_DIR=$leveldb_dir/include \
-DLEVELDB_LIBRARY=$leveldb_dir/lib/libleveldb.dll.a \
-DLIBGD_INCLUDE_DIR=$libgd_dir/include \
-DLIBGD_LIBRARY=$libgd_dir/lib/libgd.dll.a \
-DSQLITE3_INCLUDE_DIR=$sqlite_dir/include \
-DSQLITE3_LIBRARY=$sqlite_dir/lib/libsqlite3.dll.a \
-DZLIB_INCLUDE_DIR=$zlib_dir/include \
-DZLIB_LIBRARY=$zlib_dir/lib/libz.dll.a \
-DZSTD_INCLUDE_DIR=$zstd_dir/include \
-DZSTD_LIBRARY=$zstd_dir/lib/libzstd.dll.a \
make -C build -j4
mkdir pack
cp -p \
AUTHORS colors.txt COPYING README.rst \
build/minetestmapper.exe \
$leveldb_dir/bin/libleveldb.dll \
$libgd_dir/bin/libgd*.dll \
$sqlite_dir/bin/libsqlite*.dll \
$zlib_dir/bin/zlib1.dll \
$zstd_dir/bin/libzstd.dll \
"${extradlls[@]}" \
pack/
zipfile=$PWD/minetestmapper-$variant.zip
(cd pack; zip -9r "$zipfile" *)
rm -rf build pack
echo "Done."

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."

32
util/ci/script.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash -e
install_linux_deps() {
local pkgs=(cmake libgd-dev libsqlite3-dev libleveldb-dev libpq-dev libhiredis-dev libzstd-dev)
sudo apt-get update
sudo apt-get remove -y 'libgd3' nginx || : # ????
sudo apt-get install -y --no-install-recommends "${pkgs[@]}" "$@"
}
run_build() {
local args=(
-DCMAKE_BUILD_TYPE=Debug
-DENABLE_LEVELDB=1 -DENABLE_POSTGRESQL=1 -DENABLE_REDIS=1
)
[[ "$CXX" == clang* ]] && args+=(-DCMAKE_CXX_FLAGS="-fsanitize=address")
cmake . "${args[@]}"
make -j2
}
do_functional_test() {
mkdir testmap
echo "backend = sqlite3" >testmap/world.mt
sqlite3 testmap/map.sqlite <<END
CREATE TABLE blocks(pos INT,data BLOB);
INSERT INTO blocks(pos, data) VALUES(0, x'$(cat util/ci/test_block)');
END
./minetestmapper --noemptyimage -i ./testmap -o map.png
file map.png
}

1
util/ci/test_block Normal file
View File

@ -0,0 +1 @@
1b00ffff020278daedd4c1090000080331dd7f691710faf12589235cb12ae870fca6bffefaebafbffefaebafbffefaebbff7b708fdf1ffd11ffdd11ffdd11ffd01000000000000003836d59f010578da63000000010001000000ffffffff000002000000036169720001000d64656661756c743a73746f6e650a0000

61
util/dumpnodes/init.lua Normal file
View File

@ -0,0 +1,61 @@
local function get_tile(tiles, n)
local tile = tiles[n]
if type(tile) == 'table' then
return tile.name or tile.image
end
return tile
end
local function pairs_s(dict)
local keys = {}
for k in pairs(dict) do
keys[#keys+1] = k
end
table.sort(keys)
return ipairs(keys)
end
minetest.register_chatcommand("dumpnodes", {
description = "Dump node and texture list for use with minetestmapper",
func = function()
local ntbl = {}
for _, nn in pairs_s(minetest.registered_nodes) do
local prefix, name = nn:match('(.*):(.*)')
if prefix == nil or name == nil then
print("ignored(1): " .. nn)
else
if ntbl[prefix] == nil then
ntbl[prefix] = {}
end
ntbl[prefix][name] = true
end
end
local out, err = io.open(minetest.get_worldpath() .. "/nodes.txt", 'wb')
if not out then
return true, err
end
local n = 0
for _, prefix in pairs_s(ntbl) do
out:write('# ' .. prefix .. '\n')
for _, name in pairs_s(ntbl[prefix]) do
local nn = prefix .. ":" .. name
local nd = minetest.registered_nodes[nn]
local tiles = nd.tiles or nd.tile_images
if tiles == nil or nd.drawtype == 'airlike' then
print("ignored(2): " .. nn)
else
local tex = get_tile(tiles, 1)
tex = (tex .. '^'):match('%(*(.-)%)*^') -- strip modifiers
if tex:find("[combine", 1, true) then
tex = tex:match('.-=([^:]-)') -- extract first texture
end
out:write(nn .. ' ' .. tex .. '\n')
n = n + 1
end
end
out:write('\n')
end
out:close()
return true, n .. " nodes dumped."
end,
})

2
util/dumpnodes/mod.conf Normal file
View File

@ -0,0 +1,2 @@
name = dumpnodes
description = minetestmapper development mod (node dumper)

183
util/generate_colorstxt.py Executable file
View File

@ -0,0 +1,183 @@
#!/usr/bin/env python3
import sys
import os.path
import getopt
import re
from math import sqrt
try:
from PIL import Image
except:
print("Could not load image routines, install PIL ('pillow' on pypi)!", file=sys.stderr)
exit(1)
############
############
# Instructions for generating a colors.txt file for custom games and/or mods:
# 1) Add the dumpnodes mod to a Minetest world with the chosen game and mods enabled.
# 2) Join ingame and run the /dumpnodes chat command.
# 3) Run this script and poin it to the installation path of the game using -g,
# the path(s) where mods are stored using -m and the nodes.txt in your world folder.
# Example command line:
# ./util/generate_colorstxt.py --game /usr/share/minetest/games/minetest_game \
# -m ~/.minetest/mods ~/.minetest/worlds/my_world/nodes.txt
# 4) Copy the resulting colors.txt file to your world folder or to any other places
# and use it with minetestmapper's --colors option.
###########
###########
# minimal sed syntax, s|match|replace| and /match/d supported
REPLACEMENTS = [
# Delete some nodes that are usually hidden
r'/^fireflies:firefly /d',
r'/^butterflies:butterfly_/d',
# Nicer colors for water and lava
r's/^(default:(river_)?water_(flowing|source)) [0-9 ]+$/\1 39 66 106 128 224/',
r's/^(default:lava_(flowing|source)) [0-9 ]+$/\1 255 100 0/',
# Transparency for glass nodes and panes
r's/^(default:.*glass) ([0-9 ]+)$/\1 \2 64 16/',
r's/^(doors:.*glass[^ ]*) ([0-9 ]+)$/\1 \2 64 16/',
r's/^(xpanes:.*(pane|bar)[^ ]*) ([0-9 ]+)$/\1 \3 64 16/',
]
def usage():
print("Usage: generate_colorstxt.py [options] [input file] [output file]")
print("If not specified the input file defaults to ./nodes.txt and the output file to ./colors.txt")
print(" -g / --game <folder>\t\tSet path to the game (for textures), required")
print(" -m / --mods <folder>\t\tAdd search path for mod textures")
print(" --replace <file>\t\tLoad replacements from file (ADVANCED)")
def collect_files(path):
dirs = []
with os.scandir(path) as it:
for entry in it:
if entry.name[0] == '.': continue
if entry.is_dir():
dirs.append(entry.path)
continue
if entry.is_file() and '.' in entry.name:
if entry.name not in textures.keys():
textures[entry.name] = entry.path
for path2 in dirs:
collect_files(path2)
def average_color(filename):
inp = Image.open(filename).convert('RGBA')
data = inp.load()
c0, c1, c2 = [], [], []
for x in range(inp.size[0]):
for y in range(inp.size[1]):
px = data[x, y]
if px[3] < 128: continue # alpha
c0.append(px[0]**2)
c1.append(px[1]**2)
c2.append(px[2]**2)
if len(c0) == 0:
print(f"didn't find color for '{os.path.basename(filename)}'", file=sys.stderr)
return "0 0 0"
c0 = sqrt(sum(c0) / len(c0))
c1 = sqrt(sum(c1) / len(c1))
c2 = sqrt(sum(c2) / len(c2))
return "%d %d %d" % (c0, c1, c2)
def apply_sed(line, exprs):
for expr in exprs:
if expr[0] == '/':
if not expr.endswith("/d"): raise ValueError()
if re.search(expr[1:-2], line):
return ''
elif expr[0] == 's':
expr = expr.split(expr[1])
if len(expr) != 4 or expr[3] != '': raise ValueError()
line = re.sub(expr[1], expr[2], line)
else:
raise ValueError()
return line
#
try:
opts, args = getopt.getopt(sys.argv[1:], "hg:m:", ["help", "game=", "mods=", "replace="])
except getopt.GetoptError as e:
print(str(e))
exit(1)
if ('-h', '') in opts or ('--help', '') in opts:
usage()
exit(0)
input_file = "./nodes.txt"
output_file = "./colors.txt"
texturepaths = []
try:
gamepath = next(o[1] for o in opts if o[0] in ('-g', '--game'))
if not os.path.isdir(os.path.join(gamepath, "mods")):
print(f"'{gamepath}' doesn't exist or does not contain a game.", file=sys.stderr)
exit(1)
texturepaths.append(os.path.join(gamepath, "mods"))
except StopIteration:
print("No game path set but one is required. (see --help)", file=sys.stderr)
exit(1)
try:
tmp = next(o[1] for o in opts if o[0] == "--replace")
REPLACEMENTS.clear()
with open(tmp, 'r') as f:
for line in f:
if not line or line[0] == '#': continue
REPLACEMENTS.append(line.strip())
except StopIteration:
pass
for o in opts:
if o[0] not in ('-m', '--mods'): continue
if not os.path.isdir(o[1]):
print(f"Given path '{o[1]}' does not exist.'", file=sys.stderr)
exit(1)
texturepaths.append(o[1])
if len(args) > 2:
print("Too many arguments.", file=sys.stderr)
exit(1)
if len(args) > 1:
output_file = args[1]
if len(args) > 0:
input_file = args[0]
if not os.path.exists(input_file) or os.path.isdir(input_file):
print(f"Input file '{input_file}' does not exist.", file=sys.stderr)
exit(1)
#
print(f"Collecting textures from {len(texturepaths)} path(s)... ", end="", flush=True)
textures = {}
for path in texturepaths:
collect_files(path)
print("done")
print("Processing nodes...")
fin = open(input_file, 'r')
fout = open(output_file, 'w')
n = 0
for line in fin:
line = line.rstrip('\r\n')
if not line or line[0] == '#':
fout.write(line + '\n')
continue
node, tex = line.split(" ")
if not tex or tex == "blank.png":
continue
elif tex not in textures.keys():
print(f"skip {node} texture not found")
continue
color = average_color(textures[tex])
line = f"{node} {color}"
#print(f"ok {node}")
line = apply_sed(line, REPLACEMENTS)
if line:
fout.write(line + '\n')
n += 1
fin.close()
fout.close()
print(f"Done, {n} entries written.")

View File

@ -1,8 +0,0 @@
#!/bin/bash -e
mkdir -p travisbuild
cd travisbuild
cmake .. \
-DENABLE_LEVELDB=1
make -j2