From 4d9f29afaba5f0eddf34ac73614b36fabd0d5353 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 3 Feb 2015 20:15:58 +0100 Subject: Add mipmap support to allow zooming out --- src/Info.cpp | 55 ++++++++++++++++--------- src/Info.hpp | 38 ++++++++++++----- src/MinedMap.cpp | 118 ++++++++++++++++++++++++++++++++++++++++++++++++----- src/PNG.cpp | 2 +- viewer/MinedMap.js | 35 +++++++++------- viewer/index.html | 2 +- 6 files changed, 193 insertions(+), 57 deletions(-) diff --git a/src/Info.cpp b/src/Info.cpp index 530d6b2..5d4bd71 100644 --- a/src/Info.cpp +++ b/src/Info.cpp @@ -34,7 +34,7 @@ namespace MinedMap { -void Info::writeJSON(const char *filename) { +void Info::writeJSON(const char *filename) const { const std::string tmpfile = std::string(filename) + ".tmp"; FILE *f = fopen(tmpfile.c_str(), "w"); @@ -44,28 +44,43 @@ void Info::writeJSON(const char *filename) { } fprintf(f, "{\n"); - fprintf(f, " \"info\" : {\n"); - fprintf(f, " \"minX\" : %i,\n", minX); - fprintf(f, " \"maxX\" : %i,\n", maxX); - fprintf(f, " \"minZ\" : %i,\n", minZ); - fprintf(f, " \"maxZ\" : %i\n", maxZ); - fprintf(f, " },\n"); - fprintf(f, " \"regions\" : [\n"); - - for (int z = minZ; z <= maxZ; z++) { - fprintf(f, " ["); - - for (int x = minX; x <= maxX; x++) { - fprintf(f, "%s", regions.count(std::make_pair(x, z)) ? "true" : "false"); - - if (x < maxX) - fprintf(f, ", "); + fprintf(f, " \"mipmaps\" : [\n"); + + for (size_t level = 0; level < regions.size(); level++) { + int minX, maxX, minZ, maxZ; + std::tie(minX, maxX, minZ, maxZ) = getBounds(level); + + fprintf(f, " {\n"); + fprintf(f, " \"info\" : {\n"); + fprintf(f, " \"minX\" : %i,\n", minX); + fprintf(f, " \"maxX\" : %i,\n", maxX); + fprintf(f, " \"minZ\" : %i,\n", minZ); + fprintf(f, " \"maxZ\" : %i\n", maxZ); + fprintf(f, " },\n"); + fprintf(f, " \"regions\" : [\n"); + + for (int z = minZ; z <= maxZ; z++) { + fprintf(f, " ["); + + for (int x = minX; x <= maxX; x++) { + fprintf(f, "%s", regions[level].count(std::make_pair(x, z)) ? "true" : "false"); + + if (x < maxX) + fprintf(f, ", "); + } + + if (z < maxZ) + fprintf(f, "],\n"); + else + fprintf(f, "]\n"); } - if (z < maxZ) - fprintf(f, "],\n"); + fprintf(f, " ]\n"); + + if (level < regions.size() - 1) + fprintf(f, " },\n"); else - fprintf(f, "]\n"); + fprintf(f, " }\n"); } fprintf(f, " ],\n"); diff --git a/src/Info.hpp b/src/Info.hpp index 478e858..b7d040f 100644 --- a/src/Info.hpp +++ b/src/Info.hpp @@ -31,34 +31,52 @@ #include #include #include +#include namespace MinedMap { class Info { private: - std::set> regions; - int minX, maxX, minZ, maxZ; + std::vector>> regions; + std::vector> bounds; int32_t spawnX, spawnZ; public: - Info() : minX(INT_MAX), maxX(INT_MIN), minZ(INT_MAX), maxZ(INT_MIN), spawnX(0), spawnZ(0) {} + Info() : spawnX(0), spawnZ(0) { + addMipmapLevel(); + } + + std::tuple getBounds(size_t level) const { + return bounds[level]; + } + + void addRegion(int x, int z, size_t level) { + regions[level].insert(std::make_pair(x, z)); - void addRegion(int x, int z) { - regions.insert(std::make_pair(x, z)); + std::tuple &b = bounds[level]; + + if (x < std::get<0>(b)) std::get<0>(b) = x; + if (x > std::get<1>(b)) std::get<1>(b) = x; + if (z < std::get<2>(b)) std::get<2>(b) = z; + if (z > std::get<3>(b)) std::get<3>(b) = z; + } + + void addMipmapLevel() { + regions.emplace_back(); + bounds.emplace_back(INT_MAX, INT_MIN, INT_MAX, INT_MIN); + } - if (x < minX) minX = x; - if (x > maxX) maxX = x; - if (z < minZ) minZ = z; - if (z > maxZ) maxZ = z; + size_t getMipmapLevel() const { + return regions.size()-1; } void setSpawn(const std::pair &v) { std::tie(spawnX, spawnZ) = v; } - void writeJSON(const char *filename); + void writeJSON(const char *filename) const; }; } diff --git a/src/MinedMap.cpp b/src/MinedMap.cpp index 4ac39e1..8f548b6 100644 --- a/src/MinedMap.cpp +++ b/src/MinedMap.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -138,18 +139,26 @@ static void doRegion(const std::string &input, const std::string &output, const } } +template +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(); +} + static bool checkFilename(const char *name, int *x, int *z) { if (std::sscanf(name, "r.%i.%i.mca", x, z) != 2) return false; - size_t l = strlen(name) + 1; - char buf[l]; - std::snprintf(buf, l, "r.%i.%i.mca", *x, *z); - if (std::memcmp(name, buf, l)) - return false; - - return true; - + return (std::string(name) == formatTileName(*x, *z, "mca")); } static void makeDir(const std::string &name) { @@ -157,7 +166,94 @@ static void makeDir(const std::string &name) { throw std::system_error(errno, std::generic_category(), "unable to create directory " + name); } -static void makeMipmaps(const std::string &outputdir, const Info &info) { +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(); + + struct timespec t; + struct stat s; + size_t count = 0; + for (auto name : {&nw, &ne, &sw, &se}) { + if (stat(*name, &s) < 0) { + *name = nullptr; + continue; + } + + if (!count || s.st_mtim > t) + t = s.st_mtim; + + count++; + } + + std::string output = outdir + formatTileName(x, z, "png"); + if (stat(output.c_str(), &s) == 0) { + ret = true; + + if (!count || t <= s.st_mtim) + return ret; + } + + if (!count) + return ret; + + const std::string tmpfile = output + ".tmp"; + + try { + PNG::mipmap(tmpfile.c_str(), DIM, DIM, colored, nw, ne, sw, se); + + struct timespec times[2] = {t, t}; + if (utimensat(AT_FDCWD, tmpfile.c_str(), times, 0) < 0) + std::fprintf(stderr, "Warning: failed to set utime on %s: %s\n", tmpfile.c_str(), std::strerror(errno)); + + 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)); + unlink(tmpfile.c_str()); + } + } + catch (const std::exception& ex) { + unlink(tmpfile.c_str()); + 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); + } + } + } } } @@ -196,7 +292,7 @@ int main(int argc, char *argv[]) { if (!checkFilename(entry->d_name, &x, &z)) continue; - info.addRegion(x, z); + info.addRegion(x, z, 0); std::string name(entry->d_name), outname = name.substr(0, name.length()-3) + "png"; doRegion(regiondir + "/" + name, outputdir + "/map/0/" + outname, outputdir + "/light/0/" + outname); @@ -207,7 +303,7 @@ int main(int argc, char *argv[]) { World::Level level((inputdir + "/level.dat").c_str()); info.setSpawn(level.getSpawn()); - makeMipmaps(outputdir, info); + makeMipmaps(outputdir, &info); info.writeJSON((outputdir + "/info.json").c_str()); diff --git a/src/PNG.cpp b/src/PNG.cpp index 9edf88c..f56c870 100644 --- a/src/PNG.cpp +++ b/src/PNG.cpp @@ -147,7 +147,7 @@ void mipmap(const char *output, size_t width, size_t height, bool colored, const readScaled(data, 0, height/2, sw, width, height, colored); readScaled(data, width/2, height/2, se, width, height, colored); - write(output, data, width, height, true); + write(output, data, width, height, colored); } } diff --git a/viewer/MinedMap.js b/viewer/MinedMap.js index c8a41df..a949cce 100644 --- a/viewer/MinedMap.js +++ b/viewer/MinedMap.js @@ -1,7 +1,6 @@ var MinedMapLayer = L.GridLayer.extend({ - initialize: function (info, regions, layer) { - this._info = info; - this._regions = regions; + initialize: function (mipmaps, layer) { + this._mipmaps = mipmaps; this._layer = layer; this.options.attribution = 'Generated by MinedMap'; @@ -19,10 +18,19 @@ var MinedMapLayer = L.GridLayer.extend({ tile.alt = ''; - if (coords.x >= this._info.minX && coords.x <= this._info.maxX && - coords.y >= this._info.minZ && coords.y <= this._info.maxZ && - this._regions[coords.y-this._info.minZ][coords.x-this._info.minX]) - tile.src = 'data/'+this._layer+'/0/r.'+coords.x+'.'+coords.y+'.png'; + var z = -coords.z; + if (z < 0) + z = 0; + + var mipmap = this._mipmaps[z]; + + if (coords.x >= mipmap.info.minX && coords.x <= mipmap.info.maxX && + coords.y >= mipmap.info.minZ && coords.y <= mipmap.info.maxZ && + mipmap.regions[coords.y-mipmap.info.minZ][coords.x-mipmap.info.minX]) + tile.src = 'data/'+this._layer+'/'+z+'/r.'+coords.x+'.'+coords.y+'.png'; + + if (coords.z >= 0) + L.DomUtil.addClass(tile, 'overzoomed'); return tile; }, @@ -70,24 +78,23 @@ window.createMap = function () { var xhr = new XMLHttpRequest(); xhr.onload = function () { var res = JSON.parse(this.responseText), - info = res.info, - regions = res.regions, + mipmaps = res.mipmaps, spawn = res.spawn; var map = L.map('map', { center: [-spawn.z, spawn.x], zoom: 0, - minZoom: 0, + minZoom: -(mipmaps.length-1), maxZoom: 3, crs: L.CRS.Simple, maxBounds: [ - [-512*(info.maxZ+1), 512*info.minX], - [-512*info.minZ, 512*(info.maxX+1)], + [-512*(mipmaps[0].info.maxZ+1), 512*mipmaps[0].info.minX], + [-512*mipmaps[0].info.minZ, 512*(mipmaps[0].info.maxX+1)], ], }); - var mapLayer = new MinedMapLayer(info, regions, 'map'); - var lightLayer = new MinedMapLayer(info, regions, 'light'); + var mapLayer = new MinedMapLayer(mipmaps, 'map'); + var lightLayer = new MinedMapLayer(mipmaps, 'light'); mapLayer.addTo(map); diff --git a/viewer/index.html b/viewer/index.html index 44681d0..245806e 100644 --- a/viewer/index.html +++ b/viewer/index.html @@ -22,7 +22,7 @@ background: #333; } - img.leaflet-tile { + img.overzoomed { image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; -- cgit v1.2.3