mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-04 17:23:33 +01:00
Add mipmap support to allow zooming out
This commit is contained in:
parent
0162c236fa
commit
4d9f29afab
6 changed files with 190 additions and 54 deletions
49
src/Info.cpp
49
src/Info.cpp
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
namespace MinedMap {
|
namespace MinedMap {
|
||||||
|
|
||||||
void Info::writeJSON(const char *filename) {
|
void Info::writeJSON(const char *filename) const {
|
||||||
const std::string tmpfile = std::string(filename) + ".tmp";
|
const std::string tmpfile = std::string(filename) + ".tmp";
|
||||||
|
|
||||||
FILE *f = fopen(tmpfile.c_str(), "w");
|
FILE *f = fopen(tmpfile.c_str(), "w");
|
||||||
|
@ -44,28 +44,43 @@ void Info::writeJSON(const char *filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(f, "{\n");
|
fprintf(f, "{\n");
|
||||||
fprintf(f, " \"info\" : {\n");
|
fprintf(f, " \"mipmaps\" : [\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++) {
|
for (size_t level = 0; level < regions.size(); level++) {
|
||||||
fprintf(f, " [");
|
int minX, maxX, minZ, maxZ;
|
||||||
|
std::tie(minX, maxX, minZ, maxZ) = getBounds(level);
|
||||||
|
|
||||||
for (int x = minX; x <= maxX; x++) {
|
fprintf(f, " {\n");
|
||||||
fprintf(f, "%s", regions.count(std::make_pair(x, z)) ? "true" : "false");
|
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");
|
||||||
|
|
||||||
if (x < maxX)
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
fprintf(f, ", ");
|
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
|
else
|
||||||
fprintf(f, "]\n");
|
fprintf(f, " }\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(f, " ],\n");
|
fprintf(f, " ],\n");
|
||||||
|
|
38
src/Info.hpp
38
src/Info.hpp
|
@ -31,34 +31,52 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
namespace MinedMap {
|
namespace MinedMap {
|
||||||
|
|
||||||
class Info {
|
class Info {
|
||||||
private:
|
private:
|
||||||
std::set<std::pair<int, int>> regions;
|
std::vector<std::set<std::pair<int, int>>> regions;
|
||||||
int minX, maxX, minZ, maxZ;
|
std::vector<std::tuple<int, int, int, int>> bounds;
|
||||||
|
|
||||||
int32_t spawnX, spawnZ;
|
int32_t spawnX, spawnZ;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Info() : minX(INT_MAX), maxX(INT_MIN), minZ(INT_MAX), maxZ(INT_MIN), spawnX(0), spawnZ(0) {}
|
Info() : spawnX(0), spawnZ(0) {
|
||||||
|
addMipmapLevel();
|
||||||
|
}
|
||||||
|
|
||||||
void addRegion(int x, int z) {
|
std::tuple<int, int, int, int> getBounds(size_t level) const {
|
||||||
regions.insert(std::make_pair(x, z));
|
return bounds[level];
|
||||||
|
}
|
||||||
|
|
||||||
if (x < minX) minX = x;
|
void addRegion(int x, int z, size_t level) {
|
||||||
if (x > maxX) maxX = x;
|
regions[level].insert(std::make_pair(x, z));
|
||||||
if (z < minZ) minZ = z;
|
|
||||||
if (z > maxZ) maxZ = z;
|
std::tuple<int, int, int, int> &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);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getMipmapLevel() const {
|
||||||
|
return regions.size()-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSpawn(const std::pair<int32_t, int32_t> &v) {
|
void setSpawn(const std::pair<int32_t, int32_t> &v) {
|
||||||
std::tie(spawnX, spawnZ) = v;
|
std::tie(spawnX, spawnZ) = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeJSON(const char *filename);
|
void writeJSON(const char *filename) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
118
src/MinedMap.cpp
118
src/MinedMap.cpp
|
@ -35,6 +35,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
@ -138,18 +139,26 @@ static void doRegion(const std::string &input, const std::string &output, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
static bool checkFilename(const char *name, int *x, int *z) {
|
static bool checkFilename(const char *name, int *x, int *z) {
|
||||||
if (std::sscanf(name, "r.%i.%i.mca", x, z) != 2)
|
if (std::sscanf(name, "r.%i.%i.mca", x, z) != 2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
size_t l = strlen(name) + 1;
|
return (std::string(name) == formatTileName(*x, *z, "mca"));
|
||||||
char buf[l];
|
|
||||||
std::snprintf(buf, l, "r.%i.%i.mca", *x, *z);
|
|
||||||
if (std::memcmp(name, buf, l))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void makeDir(const std::string &name) {
|
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);
|
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))
|
if (!checkFilename(entry->d_name, &x, &z))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
info.addRegion(x, z);
|
info.addRegion(x, z, 0);
|
||||||
|
|
||||||
std::string name(entry->d_name), outname = name.substr(0, name.length()-3) + "png";
|
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);
|
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());
|
World::Level level((inputdir + "/level.dat").c_str());
|
||||||
info.setSpawn(level.getSpawn());
|
info.setSpawn(level.getSpawn());
|
||||||
|
|
||||||
makeMipmaps(outputdir, info);
|
makeMipmaps(outputdir, &info);
|
||||||
|
|
||||||
info.writeJSON((outputdir + "/info.json").c_str());
|
info.writeJSON((outputdir + "/info.json").c_str());
|
||||||
|
|
||||||
|
|
|
@ -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, 0, height/2, sw, width, height, colored);
|
||||||
readScaled(data, width/2, height/2, se, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
var MinedMapLayer = L.GridLayer.extend({
|
var MinedMapLayer = L.GridLayer.extend({
|
||||||
initialize: function (info, regions, layer) {
|
initialize: function (mipmaps, layer) {
|
||||||
this._info = info;
|
this._mipmaps = mipmaps;
|
||||||
this._regions = regions;
|
|
||||||
this._layer = layer;
|
this._layer = layer;
|
||||||
|
|
||||||
this.options.attribution = 'Generated by <a href="http://git.universe-factory.net/MinedMap/">MinedMap</a>';
|
this.options.attribution = 'Generated by <a href="http://git.universe-factory.net/MinedMap/">MinedMap</a>';
|
||||||
|
@ -19,10 +18,19 @@ var MinedMapLayer = L.GridLayer.extend({
|
||||||
|
|
||||||
tile.alt = '';
|
tile.alt = '';
|
||||||
|
|
||||||
if (coords.x >= this._info.minX && coords.x <= this._info.maxX &&
|
var z = -coords.z;
|
||||||
coords.y >= this._info.minZ && coords.y <= this._info.maxZ &&
|
if (z < 0)
|
||||||
this._regions[coords.y-this._info.minZ][coords.x-this._info.minX])
|
z = 0;
|
||||||
tile.src = 'data/'+this._layer+'/0/r.'+coords.x+'.'+coords.y+'.png';
|
|
||||||
|
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;
|
return tile;
|
||||||
},
|
},
|
||||||
|
@ -70,24 +78,23 @@ window.createMap = function () {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
var res = JSON.parse(this.responseText),
|
var res = JSON.parse(this.responseText),
|
||||||
info = res.info,
|
mipmaps = res.mipmaps,
|
||||||
regions = res.regions,
|
|
||||||
spawn = res.spawn;
|
spawn = res.spawn;
|
||||||
|
|
||||||
var map = L.map('map', {
|
var map = L.map('map', {
|
||||||
center: [-spawn.z, spawn.x],
|
center: [-spawn.z, spawn.x],
|
||||||
zoom: 0,
|
zoom: 0,
|
||||||
minZoom: 0,
|
minZoom: -(mipmaps.length-1),
|
||||||
maxZoom: 3,
|
maxZoom: 3,
|
||||||
crs: L.CRS.Simple,
|
crs: L.CRS.Simple,
|
||||||
maxBounds: [
|
maxBounds: [
|
||||||
[-512*(info.maxZ+1), 512*info.minX],
|
[-512*(mipmaps[0].info.maxZ+1), 512*mipmaps[0].info.minX],
|
||||||
[-512*info.minZ, 512*(info.maxX+1)],
|
[-512*mipmaps[0].info.minZ, 512*(mipmaps[0].info.maxX+1)],
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
var mapLayer = new MinedMapLayer(info, regions, 'map');
|
var mapLayer = new MinedMapLayer(mipmaps, 'map');
|
||||||
var lightLayer = new MinedMapLayer(info, regions, 'light');
|
var lightLayer = new MinedMapLayer(mipmaps, 'light');
|
||||||
|
|
||||||
mapLayer.addTo(map);
|
mapLayer.addTo(map);
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
background: #333;
|
background: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.leaflet-tile {
|
img.overzoomed {
|
||||||
image-rendering: -moz-crisp-edges;
|
image-rendering: -moz-crisp-edges;
|
||||||
image-rendering: -o-crisp-edges;
|
image-rendering: -o-crisp-edges;
|
||||||
image-rendering: -webkit-optimize-contrast;
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
|
Loading…
Add table
Reference in a new issue