2015-01-31 15:07:47 +01:00
|
|
|
/*
|
|
|
|
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2015-02-02 17:56:43 +01:00
|
|
|
#include "Info.hpp"
|
2015-02-02 19:29:37 +01:00
|
|
|
#include "PNG.hpp"
|
2015-02-02 18:33:21 +01:00
|
|
|
#include "World/Level.hpp"
|
2015-02-01 00:21:17 +01:00
|
|
|
#include "World/Region.hpp"
|
|
|
|
|
2015-02-01 11:58:50 +01:00
|
|
|
#include <cerrno>
|
2017-06-13 16:39:17 +02:00
|
|
|
#include <cinttypes>
|
2015-02-02 02:25:56 +01:00
|
|
|
#include <climits>
|
2015-02-01 11:58:50 +01:00
|
|
|
#include <cstdio>
|
2015-02-02 02:25:56 +01:00
|
|
|
#include <cstring>
|
|
|
|
#include <cstdlib>
|
2015-02-01 00:21:17 +01:00
|
|
|
#include <iostream>
|
2019-12-14 22:15:45 +01:00
|
|
|
#include <stdexcept>
|
2015-02-03 20:15:58 +01:00
|
|
|
#include <sstream>
|
2015-02-03 12:41:47 +01:00
|
|
|
#include <system_error>
|
2015-02-01 00:21:17 +01:00
|
|
|
|
2015-02-02 02:25:56 +01:00
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include <dirent.h>
|
2015-02-02 03:06:22 +01:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
2015-02-01 00:21:17 +01:00
|
|
|
|
|
|
|
|
2015-02-03 12:41:47 +01:00
|
|
|
namespace MinedMap {
|
2015-02-01 11:58:50 +01:00
|
|
|
|
2015-02-02 01:41:17 +01:00
|
|
|
static const size_t DIM = World::Region::SIZE*World::Chunk::SIZE;
|
|
|
|
|
|
|
|
|
2018-11-08 17:12:40 +01:00
|
|
|
static void addChunk(Resource::Color image[DIM*DIM], uint8_t lightmap[2*DIM*DIM], size_t X, size_t Z, const World::ChunkData *data) {
|
2018-07-20 23:33:11 +02:00
|
|
|
World::Chunk chunk(data);
|
|
|
|
World::Chunk::Blocks layer = chunk.getTopLayer();
|
2015-02-02 01:41:17 +01:00
|
|
|
|
|
|
|
for (size_t x = 0; x < World::Chunk::SIZE; x++) {
|
2015-02-03 13:13:12 +01:00
|
|
|
for (size_t z = 0; z < World::Chunk::SIZE; z++) {
|
|
|
|
size_t i = (Z*World::Chunk::SIZE+z)*DIM + X*World::Chunk::SIZE+x;
|
|
|
|
const World::Block &block = layer.blocks[x][z];
|
|
|
|
|
2018-07-26 23:14:03 +02:00
|
|
|
image[i] = block.getColor();
|
2018-07-22 16:59:18 +02:00
|
|
|
lightmap[2*i+1] = (1 - block.blockLight/15.f)*192;
|
2015-02-03 13:13:12 +01:00
|
|
|
}
|
2015-02-02 01:41:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
static int64_t readStamp(const std::string &filename) {
|
|
|
|
int64_t v = INT64_MIN;
|
|
|
|
|
|
|
|
std::FILE *f = std::fopen((filename + ".stamp").c_str(), "r");
|
|
|
|
if (f) {
|
|
|
|
std::fscanf(f, "%" SCNd64, &v);
|
|
|
|
std::fclose(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void writeStamp(const std::string &filename, int64_t v) {
|
|
|
|
std::FILE *f = std::fopen((filename + ".stamp").c_str(), "w");
|
|
|
|
if (f) {
|
|
|
|
std::fprintf(f, "%" PRId64, v);
|
|
|
|
std::fclose(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void writeImage(const std::string &output, const uint8_t *data, bool colored, int64_t t) {
|
2015-02-03 15:02:21 +01:00
|
|
|
const std::string tmpfile = output + ".tmp";
|
|
|
|
|
|
|
|
try {
|
|
|
|
PNG::write(tmpfile.c_str(), data, DIM, DIM, colored);
|
|
|
|
|
|
|
|
if (std::rename(tmpfile.c_str(), output.c_str()) < 0) {
|
|
|
|
std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno));
|
2015-02-06 19:51:20 +01:00
|
|
|
std::remove(tmpfile.c_str());
|
2015-02-03 15:02:21 +01:00
|
|
|
}
|
2017-06-13 16:39:17 +02:00
|
|
|
|
|
|
|
writeStamp(output, t);
|
2019-12-14 22:15:45 +01:00
|
|
|
} catch (const std::exception& ex) {
|
2015-02-06 19:51:20 +01:00
|
|
|
std::remove(tmpfile.c_str());
|
2015-02-03 15:02:21 +01:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-03 13:13:12 +01:00
|
|
|
static void doRegion(const std::string &input, const std::string &output, const std::string &output_light) {
|
2017-06-13 16:39:17 +02:00
|
|
|
int64_t intime;
|
2015-02-02 03:06:22 +01:00
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
{
|
|
|
|
struct stat instat;
|
2015-02-02 03:06:22 +01:00
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
if (stat(input.c_str(), &instat) < 0) {
|
2020-06-19 19:44:50 +02:00
|
|
|
if (errno != ENOENT)
|
|
|
|
std::fprintf(stderr, "Unable to stat %s: %s\n", input.c_str(), std::strerror(errno));
|
2015-02-03 18:39:58 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-06-13 16:39:17 +02:00
|
|
|
|
2018-07-26 23:15:32 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
intime = (int64_t)instat.st_mtime * 1000000;
|
|
|
|
#else
|
2017-06-13 16:39:17 +02:00
|
|
|
intime = (int64_t)instat.st_mtim.tv_sec * 1000000 + instat.st_mtim.tv_nsec / 1000;
|
2018-07-26 23:15:32 +02:00
|
|
|
#endif
|
2017-06-13 16:39:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
struct stat s;
|
|
|
|
if (stat(output.c_str(), &s) == 0) {
|
|
|
|
int64_t outtime = readStamp(output);
|
|
|
|
if (intime <= outtime) {
|
|
|
|
std::printf("%s is up-to-date.\n", output.c_str());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2015-02-02 03:06:22 +01:00
|
|
|
}
|
|
|
|
|
2015-02-02 03:53:49 +01:00
|
|
|
std::printf("Generating %s from %s...\n", output.c_str(), input.c_str());
|
2015-02-02 02:25:56 +01:00
|
|
|
|
2015-02-02 03:38:49 +01:00
|
|
|
try {
|
2018-11-08 17:12:40 +01:00
|
|
|
std::unique_ptr<Resource::Color[]> image(new Resource::Color[DIM*DIM]);
|
2015-02-04 09:00:34 +01:00
|
|
|
std::memset(image.get(), 0, 4*DIM*DIM);
|
2015-02-02 03:06:22 +01:00
|
|
|
|
2015-02-04 09:00:34 +01:00
|
|
|
std::unique_ptr<uint8_t[]> lightmap(new uint8_t[2*DIM*DIM]);
|
|
|
|
std::memset(lightmap.get(), 0, 2*DIM*DIM);
|
|
|
|
|
2018-07-20 23:33:11 +02:00
|
|
|
World::Region::visitChunks(input.c_str(), [&] (size_t X, size_t Z, const World::ChunkData *chunk) { addChunk(image.get(), lightmap.get(), X, Z, chunk); });
|
2015-02-04 09:00:34 +01:00
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
writeImage(output, reinterpret_cast<const uint8_t*>(image.get()), true, intime);
|
|
|
|
writeImage(output_light, lightmap.get(), false, intime);
|
2019-12-14 22:15:45 +01:00
|
|
|
} catch (const std::exception& ex) {
|
2015-02-02 03:38:49 +01:00
|
|
|
std::fprintf(stderr, "Failed to generate %s: %s\n", output.c_str(), ex.what());
|
2015-02-02 03:06:22 +01:00
|
|
|
}
|
2015-02-02 02:25:56 +01:00
|
|
|
}
|
|
|
|
|
2015-02-03 20:15:58 +01:00
|
|
|
template<typename T>
|
|
|
|
static std::string format(const T &v) {
|
|
|
|
std::ostringstream s;
|
|
|
|
|
|
|
|
s << v;
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::string formatTileName(int x, int z, const std::string &ext) {
|
|
|
|
std::ostringstream s;
|
|
|
|
|
|
|
|
s << "r." << x << "." << z << "." << ext;
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
2015-02-02 04:47:40 +01:00
|
|
|
static bool checkFilename(const char *name, int *x, int *z) {
|
|
|
|
if (std::sscanf(name, "r.%i.%i.mca", x, z) != 2)
|
|
|
|
return false;
|
|
|
|
|
2015-02-03 20:15:58 +01:00
|
|
|
return (std::string(name) == formatTileName(*x, *z, "mca"));
|
2015-02-02 04:47:40 +01:00
|
|
|
}
|
|
|
|
|
2015-02-03 12:41:47 +01:00
|
|
|
static void makeDir(const std::string &name) {
|
2018-07-26 23:15:32 +02:00
|
|
|
if (
|
|
|
|
mkdir(
|
|
|
|
name.c_str()
|
|
|
|
#ifndef _WIN32
|
|
|
|
, 0777
|
|
|
|
#endif
|
|
|
|
) < 0 && errno != EEXIST
|
|
|
|
)
|
2015-02-03 12:41:47 +01:00
|
|
|
throw std::system_error(errno, std::generic_category(), "unable to create directory " + name);
|
|
|
|
}
|
|
|
|
|
2020-06-19 19:44:50 +02:00
|
|
|
static void makeMap(const std::string ®iondir, const std::string &outputdir, size_t x, size_t z) {
|
|
|
|
std::string inname = formatTileName(x, z, "mca");
|
|
|
|
std::string outname = formatTileName(x, z, "png");
|
|
|
|
doRegion(regiondir + "/" + inname, outputdir + "/map/0/" + outname, outputdir + "/light/0/" + outname);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void makeMaps(const std::string ®iondir, const std::string &outputdir, const Info *info) {
|
|
|
|
int minX, maxX, minZ, maxZ;
|
|
|
|
std::tie(minX, maxX, minZ, maxZ) = info->getBounds(0);
|
|
|
|
|
|
|
|
for (int x = minX; x <= maxX; x++) {
|
|
|
|
for (int z = minZ; z <= maxZ; z++)
|
|
|
|
makeMap(regiondir, outputdir, x, z);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-03 20:15:58 +01:00
|
|
|
static bool makeMipmap(const std::string &dir, size_t level, size_t x, size_t z, bool colored) {
|
|
|
|
bool ret = false;
|
|
|
|
|
|
|
|
std::string indir = dir + "/" + format(level-1) + "/";
|
|
|
|
std::string outdir = dir + "/" + format(level) + "/";
|
|
|
|
|
|
|
|
const std::string nw_str = indir + formatTileName(x*2, z*2, "png");
|
|
|
|
const std::string ne_str = indir + formatTileName(x*2+1, z*2, "png");
|
|
|
|
const std::string sw_str = indir + formatTileName(x*2, z*2+1, "png");
|
|
|
|
const std::string se_str = indir + formatTileName(x*2+1, z*2+1, "png");
|
|
|
|
|
|
|
|
const char *nw = nw_str.c_str();
|
|
|
|
const char *ne = ne_str.c_str();
|
|
|
|
const char *sw = sw_str.c_str();
|
|
|
|
const char *se = se_str.c_str();
|
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
int64_t t = INT64_MIN;
|
2015-02-03 20:15:58 +01:00
|
|
|
size_t count = 0;
|
|
|
|
for (auto name : {&nw, &ne, &sw, &se}) {
|
2017-06-13 16:39:17 +02:00
|
|
|
struct stat s;
|
2015-02-03 20:15:58 +01:00
|
|
|
if (stat(*name, &s) < 0) {
|
|
|
|
*name = nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
int64_t t_part = readStamp(*name);
|
|
|
|
if (t_part > t)
|
|
|
|
t = t_part;
|
2015-02-03 20:15:58 +01:00
|
|
|
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string output = outdir + formatTileName(x, z, "png");
|
|
|
|
|
2017-06-13 16:39:17 +02:00
|
|
|
{
|
|
|
|
struct stat s;
|
|
|
|
if (stat(output.c_str(), &s) == 0) {
|
|
|
|
ret = true;
|
|
|
|
|
|
|
|
int64_t outtime = readStamp(output);
|
|
|
|
if (t <= outtime)
|
|
|
|
return ret;
|
|
|
|
}
|
2015-02-03 20:15:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!count)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
const std::string tmpfile = output + ".tmp";
|
|
|
|
|
|
|
|
try {
|
|
|
|
PNG::mipmap(tmpfile.c_str(), DIM, DIM, colored, nw, ne, sw, se);
|
|
|
|
|
|
|
|
if (std::rename(tmpfile.c_str(), output.c_str()) < 0) {
|
|
|
|
std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno));
|
2015-02-06 19:51:20 +01:00
|
|
|
std::remove(tmpfile.c_str());
|
2015-02-03 20:15:58 +01:00
|
|
|
}
|
2017-06-13 16:39:17 +02:00
|
|
|
|
|
|
|
writeStamp(output, t);
|
2019-12-14 22:15:45 +01:00
|
|
|
} catch (const std::exception& ex) {
|
2015-02-06 19:51:20 +01:00
|
|
|
std::remove(tmpfile.c_str());
|
2015-02-03 20:15:58 +01:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void makeMipmaps(const std::string &dir, Info *info) {
|
|
|
|
int minX, maxX, minZ, maxZ;
|
|
|
|
std::tie(minX, maxX, minZ, maxZ) = info->getBounds(0);
|
|
|
|
|
|
|
|
while (minX < -1 || maxX > 0 || minZ < -1 || maxZ > 0) {
|
|
|
|
info->addMipmapLevel();
|
|
|
|
size_t level = info->getMipmapLevel();
|
|
|
|
makeDir(dir + "/map/" + format(level));
|
|
|
|
makeDir(dir + "/light/" + format(level));
|
|
|
|
|
|
|
|
minX = (minX-1)/2;
|
|
|
|
maxX = maxX/2;
|
|
|
|
minZ = (minZ-1)/2;
|
|
|
|
maxZ = maxZ/2;
|
|
|
|
|
|
|
|
for (int x = minX; x <= maxX; x++) {
|
|
|
|
for (int z = minZ; z <= maxZ; z++) {
|
|
|
|
if (makeMipmap(dir + "/map", level, x, z, true))
|
|
|
|
info->addRegion(x, z, level);
|
|
|
|
|
|
|
|
makeMipmap(dir + "/light", level, x, z, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-03 12:41:47 +01:00
|
|
|
}
|
|
|
|
|
2020-06-19 19:44:50 +02:00
|
|
|
static Info collectInfo(const std::string ®iondir) {
|
2015-02-02 18:33:21 +01:00
|
|
|
DIR *dir = opendir(regiondir.c_str());
|
2019-12-14 22:15:45 +01:00
|
|
|
if (!dir)
|
|
|
|
throw std::system_error(errno, std::generic_category(), "Unable to read input directory");
|
2015-02-02 02:25:56 +01:00
|
|
|
|
2015-02-02 17:56:43 +01:00
|
|
|
Info info;
|
2015-02-02 02:25:56 +01:00
|
|
|
|
|
|
|
struct dirent *entry;
|
|
|
|
while ((entry = readdir(dir)) != nullptr) {
|
2015-02-02 03:33:43 +01:00
|
|
|
int x, z;
|
2015-02-02 04:47:40 +01:00
|
|
|
if (!checkFilename(entry->d_name, &x, &z))
|
|
|
|
continue;
|
|
|
|
|
2015-02-03 20:15:58 +01:00
|
|
|
info.addRegion(x, z, 0);
|
2015-02-02 04:47:40 +01:00
|
|
|
|
2015-02-02 02:25:56 +01:00
|
|
|
}
|
2015-02-01 11:58:50 +01:00
|
|
|
|
2015-02-02 02:25:56 +01:00
|
|
|
closedir(dir);
|
2015-02-01 01:38:20 +01:00
|
|
|
|
2020-06-19 19:44:50 +02:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doLevel(const std::string &inputdir, const std::string &outputdir) {
|
|
|
|
const std::string regiondir = inputdir + "/region";
|
|
|
|
|
|
|
|
makeDir(outputdir + "/map");
|
|
|
|
makeDir(outputdir + "/map/0");
|
|
|
|
makeDir(outputdir + "/light");
|
|
|
|
makeDir(outputdir + "/light/0");
|
|
|
|
|
|
|
|
Info info = collectInfo(regiondir);
|
|
|
|
|
|
|
|
std::printf("Updating map data...\n");
|
|
|
|
makeMaps(regiondir, outputdir, &info);
|
|
|
|
|
2015-02-02 18:33:21 +01:00
|
|
|
World::Level level((inputdir + "/level.dat").c_str());
|
|
|
|
info.setSpawn(level.getSpawn());
|
|
|
|
|
2020-06-19 19:09:40 +02:00
|
|
|
std::printf("Updating mipmaps...\n");
|
2015-02-03 20:15:58 +01:00
|
|
|
makeMipmaps(outputdir, &info);
|
2015-02-03 12:41:47 +01:00
|
|
|
|
2015-02-02 17:56:43 +01:00
|
|
|
info.writeJSON((outputdir + "/info.json").c_str());
|
2019-12-14 22:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
if (argc < 3) {
|
|
|
|
std::fprintf(stderr, "Usage: %s <data directory> <output directory>\n", argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
MinedMap::doLevel(argv[1], argv[2]);
|
|
|
|
} catch (const std::runtime_error& ex) {
|
|
|
|
std::fprintf(stderr, "Error: %s\n", ex.what());
|
|
|
|
return 1;
|
|
|
|
}
|
2015-02-02 05:13:07 +01:00
|
|
|
|
2015-01-31 15:07:47 +01:00
|
|
|
return 0;
|
|
|
|
}
|